Committed by
Gerrit Code Review
ONOS-1934 - CORD-GUI -- Basic dashboard page created: gets data and displays it …
…in tables. Icon support and navigation bar added. Change-Id: I68f2f180f11f958fa0f49411b03d101204662d79
Showing
17 changed files
with
952 additions
and
113 deletions
1 | +/* | ||
2 | + * Copyright 2015 Open Networking Laboratory | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +(function () { | ||
18 | + 'use strict'; | ||
19 | + | ||
20 | + angular.module('cordGui') | ||
21 | + | ||
22 | + .directive('icon', [function () { | ||
23 | + return { | ||
24 | + restrict: 'E', | ||
25 | + compile: function (element, attrs) { | ||
26 | + var html = | ||
27 | + '<svg class="embedded-icon" width="' + attrs.size + '" ' + | ||
28 | + 'height="' + attrs.size + '" viewBox="0 0 50 50">' + | ||
29 | + '<g class="icon">' + | ||
30 | + '<rect width="50" height="50"></rect>' + | ||
31 | + '<use width="50" height="50" class="glyph ' | ||
32 | + + attrs.id + '" xlink:href="#' + attrs.id + | ||
33 | + '"></use>' + | ||
34 | + '</g>' + | ||
35 | + '</svg>'; | ||
36 | + element.replaceWith(html); | ||
37 | + } | ||
38 | + }; | ||
39 | + }]); | ||
40 | +}()); |
1 | +/* | ||
2 | + * Copyright 2015 Open Networking Laboratory | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +.nav ul { | ||
18 | + list-style-type: none; | ||
19 | + width: 100%; | ||
20 | +} | ||
21 | + | ||
22 | +.nav li { | ||
23 | + display: inline; | ||
24 | +} |
1 | +<!-- | ||
2 | + ~ Copyright 2015 Open Networking Laboratory | ||
3 | + ~ | ||
4 | + ~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + ~ you may not use this file except in compliance with the License. | ||
6 | + ~ You may obtain a copy of the License at | ||
7 | + ~ | ||
8 | + ~ http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + ~ | ||
10 | + ~ Unless required by applicable law or agreed to in writing, software | ||
11 | + ~ distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + ~ See the License for the specific language governing permissions and | ||
14 | + ~ limitations under the License. | ||
15 | + --> | ||
16 | + | ||
17 | +<div class="nav"> | ||
18 | + <ul> | ||
19 | + <a href="#/home"> | ||
20 | + <li ng-class="{selected: page === 'dashboard'}">Dashboard</li> | ||
21 | + </a> | ||
22 | + <a href="#/user"> | ||
23 | + <li ng-class="{selected: page === 'user'}">Users</li> | ||
24 | + </a> | ||
25 | + <a href="#/bundle"> | ||
26 | + <li ng-class="{selected: page === 'bundle'}">Bundles</li> | ||
27 | + </a> | ||
28 | + </ul> | ||
29 | +</div> |
1 | +/* | ||
2 | + * Copyright 2015 Open Networking Laboratory | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +angular.module('cordNav', []) | ||
18 | + .directive('nav', function () { | ||
19 | + return { | ||
20 | + restrict: 'E', | ||
21 | + templateUrl: 'app/fw/nav/nav.html' | ||
22 | + }; | ||
23 | + }); |
1 | -<!DOCTYPE html> | ||
2 | -<!-- | ||
3 | - ~ Copyright 2015 Open Networking Laboratory | ||
4 | - ~ | ||
5 | - ~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | - ~ you may not use this file except in compliance with the License. | ||
7 | - ~ You may obtain a copy of the License at | ||
8 | - ~ | ||
9 | - ~ http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | - ~ | ||
11 | - ~ Unless required by applicable law or agreed to in writing, software | ||
12 | - ~ distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | - ~ See the License for the specific language governing permissions and | ||
15 | - ~ limitations under the License. | ||
16 | - --> | ||
17 | - | ||
18 | -<html> | ||
19 | -<head lang="en"> | ||
20 | - <meta charset="UTF-8"> | ||
21 | - <title></title> | ||
22 | -</head> | ||
23 | -<body> | ||
24 | -<h2>Subscriber Bundle</h2> | ||
25 | - | ||
26 | -</body> | ||
27 | -</html> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +<!-- Bundle page partial html --> | ||
2 | +<div class="container"> | ||
3 | + <nav></nav> | ||
4 | + <h2>Subscriber Bundles</h2> | ||
5 | +</div> | ||
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
... | @@ -18,7 +18,10 @@ | ... | @@ -18,7 +18,10 @@ |
18 | 'use strict'; | 18 | 'use strict'; |
19 | 19 | ||
20 | angular.module('cordBundle', []) | 20 | angular.module('cordBundle', []) |
21 | - .controller('CordBundleCtrl', ['$log', function ($log) { | 21 | + .controller('CordBundleCtrl', ['$log', '$scope', |
22 | + function ($log, $scope) { | ||
23 | + $scope.page = 'bundle'; | ||
24 | + | ||
22 | $log.debug('Cord Bundle Ctrl has been created.'); | 25 | $log.debug('Cord Bundle Ctrl has been created.'); |
23 | }]); | 26 | }]); |
24 | 27 | ... | ... |
... | @@ -14,15 +14,28 @@ | ... | @@ -14,15 +14,28 @@ |
14 | * limitations under the License. | 14 | * limitations under the License. |
15 | */ | 15 | */ |
16 | 16 | ||
17 | -head, body, footer, h1, h2, h3, h4, h5, h6, p, div, a { | 17 | +head, body, footer, |
18 | +h1, h2, h3, h4, h5, h6, p, | ||
19 | +a, ul, li, div, | ||
20 | +table, tr, td, th, thead, tbody { | ||
18 | padding: 0; | 21 | padding: 0; |
19 | margin: 0; | 22 | margin: 0; |
20 | } | 23 | } |
21 | 24 | ||
22 | -h1, h2, h3, h4, h5, h6, p, a { | 25 | +h1, h2, h3, h4, h5, h6, |
26 | +p, a, li, th, td { | ||
23 | font-family: "Lucida Grande", "Droid Sans", Arial, Helvetica, sans-serif; | 27 | font-family: "Lucida Grande", "Droid Sans", Arial, Helvetica, sans-serif; |
24 | } | 28 | } |
25 | 29 | ||
26 | #view h2 { | 30 | #view h2 { |
27 | text-align: center; | 31 | text-align: center; |
28 | } | 32 | } |
33 | + | ||
34 | +#view div.container { | ||
35 | + width: 960px; | ||
36 | + margin: 0 auto; | ||
37 | +} | ||
38 | + | ||
39 | +svg#icon-defs { | ||
40 | + display: none; | ||
41 | +} | ... | ... |
1 | +/* | ||
2 | + * Copyright 2015 Open Networking Laboratory | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +div#db-bundle, div#db-users { | ||
18 | + float: left; | ||
19 | +} | ||
20 | + | ||
21 | +svg.embedded-icon g.icon rect { | ||
22 | + fill: none; | ||
23 | +} | ||
24 | + | ||
25 | +svg.embedded-icon g.icon .glyph.checkMark { | ||
26 | + fill: #3eff7d; | ||
27 | +} | ||
28 | +svg.embedded-icon g.icon .glyph.xMark { | ||
29 | + fill: #a81c22; | ||
30 | +} |
1 | -<!DOCTYPE html> | 1 | +<!-- Home page partial html --> |
2 | -<!-- | 2 | +<div class="container"> |
3 | - ~ Copyright 2015 Open Networking Laboratory | 3 | + <nav></nav> |
4 | - ~ | 4 | + <h2>Dashboard</h2> |
5 | - ~ Licensed under the Apache License, Version 2.0 (the "License"); | 5 | + <div id="db-bundle"> |
6 | - ~ you may not use this file except in compliance with the License. | 6 | + <table class="title"> |
7 | - ~ You may obtain a copy of the License at | 7 | + <tr> |
8 | - ~ | 8 | + <th>{{bundle.name}}</th> |
9 | - ~ http://www.apache.org/licenses/LICENSE-2.0 | 9 | + </tr> |
10 | - ~ | 10 | + </table> |
11 | - ~ Unless required by applicable law or agreed to in writing, software | 11 | + <table class="content"> |
12 | - ~ distributed under the License is distributed on an "AS IS" BASIS, | 12 | + <thead> |
13 | - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 13 | + <tr> |
14 | - ~ See the License for the specific language governing permissions and | 14 | + <th>ID</th> |
15 | - ~ limitations under the License. | 15 | + <th>Name</th> |
16 | - --> | 16 | + <th>Active</th> |
17 | + </tr> | ||
18 | + </thead> | ||
19 | + <tbody> | ||
20 | + <tr ng-repeat="func in bundle.functions"> | ||
21 | + <td>{{func.id}}</td> | ||
22 | + <td>{{func.name}}</td> | ||
23 | + <td ng-if="func.active"> | ||
24 | + <icon size="20" id="checkMark"></icon> | ||
25 | + </td> | ||
26 | + <td ng-if="!func.active"> | ||
27 | + <icon size="20" id="xMark"></icon> | ||
28 | + </td> | ||
29 | + </tr> | ||
30 | + </tbody> | ||
31 | + </table> | ||
32 | + </div> | ||
17 | 33 | ||
18 | -<html> | ||
19 | -<head lang="en"> | ||
20 | - <meta charset="UTF-8"> | ||
21 | - <title></title> | ||
22 | -</head> | ||
23 | -<body> | ||
24 | -<h2>Dashboard</h2> | ||
25 | - | ||
26 | -</body> | ||
27 | -</html> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
34 | + <div id="db-users"> | ||
35 | + <table class="title"> | ||
36 | + <tr> | ||
37 | + <th>Users</th> | ||
38 | + </tr> | ||
39 | + </table> | ||
40 | + <table class="content"> | ||
41 | + <thead> | ||
42 | + <tr> | ||
43 | + <th>ID</th> | ||
44 | + <th>Name</th> | ||
45 | + <th>Role</th> | ||
46 | + </tr> | ||
47 | + </thead> | ||
48 | + <tbody> | ||
49 | + <tr ng-repeat="user in users"> | ||
50 | + <td>{{user.id}}</td> | ||
51 | + <td>{{user.name}}</td> | ||
52 | + <td>{{user.role}}</td> | ||
53 | + </tr> | ||
54 | + </tbody> | ||
55 | + </table> | ||
56 | + </div> | ||
57 | +</div> | ... | ... |
... | @@ -17,8 +17,28 @@ | ... | @@ -17,8 +17,28 @@ |
17 | (function () { | 17 | (function () { |
18 | 'use strict'; | 18 | 'use strict'; |
19 | 19 | ||
20 | + var before = 'http://localhost:8080/rs/dashboard/0', | ||
21 | + after = 'http://localhost:8080/rs/dashboard/1'; | ||
22 | + | ||
20 | angular.module('cordHome', []) | 23 | angular.module('cordHome', []) |
21 | - .controller('CordHomeCtrl', ['$log', function ($log) { | 24 | + .controller('CordHomeCtrl', ['$log', '$scope', '$resource', |
25 | + function ($log, $scope, $resource) { | ||
26 | + var DashboardData, resource; | ||
27 | + $scope.page = 'dashboard'; | ||
28 | + | ||
29 | + DashboardData = $resource(before); | ||
30 | + resource = DashboardData.get({}, | ||
31 | + // success | ||
32 | + function () { | ||
33 | + $scope.bundle = resource.bundle; | ||
34 | + $scope.users = resource.users; | ||
35 | + }, | ||
36 | + // error | ||
37 | + function () { | ||
38 | + $log.error('Problem with resource', resource); | ||
39 | + }); | ||
40 | + $log.debug('Resource received:', resource); | ||
41 | + | ||
22 | $log.debug('Cord Home Ctrl has been created.'); | 42 | $log.debug('Cord Home Ctrl has been created.'); |
23 | }]); | 43 | }]); |
24 | }()); | 44 | }()); | ... | ... |
1 | -<!DOCTYPE html> | 1 | +<!-- Login page partial html --> |
2 | -<!-- | ||
3 | - ~ Copyright 2015 Open Networking Laboratory | ||
4 | - ~ | ||
5 | - ~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | - ~ you may not use this file except in compliance with the License. | ||
7 | - ~ You may obtain a copy of the License at | ||
8 | - ~ | ||
9 | - ~ http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | - ~ | ||
11 | - ~ Unless required by applicable law or agreed to in writing, software | ||
12 | - ~ distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | - ~ See the License for the specific language governing permissions and | ||
15 | - ~ limitations under the License. | ||
16 | - --> | ||
17 | - | ||
18 | -<html> | ||
19 | -<head lang="en"> | ||
20 | - <meta charset="UTF-8"> | ||
21 | - <title></title> | ||
22 | -</head> | ||
23 | -<body> | ||
24 | <div id="login-header"> | 2 | <div id="login-header"> |
25 | <h2>Login to Subscriber Portal</h2> | 3 | <h2>Login to Subscriber Portal</h2> |
26 | </div> | 4 | </div> |
... | @@ -34,6 +12,3 @@ | ... | @@ -34,6 +12,3 @@ |
34 | <a href="#/home"><input type="submit" value="Submit"></a> | 12 | <a href="#/home"><input type="submit" value="Submit"></a> |
35 | </form> | 13 | </form> |
36 | </div> | 14 | </div> |
... | \ No newline at end of file | ... | \ No newline at end of file |
37 | - | ||
38 | -</body> | ||
39 | -</html> | ||
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
1 | -<!DOCTYPE html> | ||
2 | -<!-- | ||
3 | - ~ Copyright 2015 Open Networking Laboratory | ||
4 | - ~ | ||
5 | - ~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | - ~ you may not use this file except in compliance with the License. | ||
7 | - ~ You may obtain a copy of the License at | ||
8 | - ~ | ||
9 | - ~ http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | - ~ | ||
11 | - ~ Unless required by applicable law or agreed to in writing, software | ||
12 | - ~ distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | - ~ See the License for the specific language governing permissions and | ||
15 | - ~ limitations under the License. | ||
16 | - --> | ||
17 | - | ||
18 | -<html> | ||
19 | -<head lang="en"> | ||
20 | - <meta charset="UTF-8"> | ||
21 | - <title></title> | ||
22 | -</head> | ||
23 | -<body> | ||
24 | -<h2>Users</h2> | ||
25 | - | ||
26 | -</body> | ||
27 | -</html> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +<!-- Users page partial html --> | ||
2 | +<div class="container"> | ||
3 | + <nav></nav> | ||
4 | + <h2>Users</h2> | ||
5 | +</div> | ||
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
... | @@ -18,7 +18,9 @@ | ... | @@ -18,7 +18,9 @@ |
18 | 'use strict'; | 18 | 'use strict'; |
19 | 19 | ||
20 | angular.module('cordUser', []) | 20 | angular.module('cordUser', []) |
21 | - .controller('CordUserCtrl', ['$log', function ($log) { | 21 | + .controller('CordUserCtrl', ['$log', '$scope', function ($log, $scope) { |
22 | + $scope.page = 'user'; | ||
23 | + | ||
22 | $log.debug('Cord User Ctrl has been created.'); | 24 | $log.debug('Cord User Ctrl has been created.'); |
23 | }]); | 25 | }]); |
24 | 26 | ... | ... |
... | @@ -24,6 +24,7 @@ | ... | @@ -24,6 +24,7 @@ |
24 | <script src="tp/angular.js"></script> | 24 | <script src="tp/angular.js"></script> |
25 | <script src="tp/angular-route.js"></script> | 25 | <script src="tp/angular-route.js"></script> |
26 | <script src="tp/angular-animate.js"></script> | 26 | <script src="tp/angular-animate.js"></script> |
27 | + <script src="tp/angular-resource.js"></script> | ||
27 | <script src="tp/jquery-2.1.4.js"></script> | 28 | <script src="tp/jquery-2.1.4.js"></script> |
28 | 29 | ||
29 | <script src="cord.js"></script> | 30 | <script src="cord.js"></script> |
... | @@ -34,10 +35,16 @@ | ... | @@ -34,10 +35,16 @@ |
34 | <script src="app/fw/foot/foot.js"></script> | 35 | <script src="app/fw/foot/foot.js"></script> |
35 | <link rel="stylesheet" href="app/fw/foot/foot.css"> | 36 | <link rel="stylesheet" href="app/fw/foot/foot.css"> |
36 | 37 | ||
38 | + <script src="app/fw/nav/nav.js"></script> | ||
39 | + <link rel="stylesheet" href="app/fw/nav/nav.css"> | ||
40 | + | ||
41 | + <script src="app/fw/icon/icon.js"></script> | ||
42 | + | ||
37 | <script src="app/view/login/login.js"></script> | 43 | <script src="app/view/login/login.js"></script> |
38 | <link rel="stylesheet" href="app/view/login/login.css"> | 44 | <link rel="stylesheet" href="app/view/login/login.css"> |
39 | 45 | ||
40 | <script src="app/view/home/home.js"></script> | 46 | <script src="app/view/home/home.js"></script> |
47 | + <link rel="stylesheet" href="app/view/home/home.css"> | ||
41 | 48 | ||
42 | <script src="app/view/user/user.js"></script> | 49 | <script src="app/view/user/user.js"></script> |
43 | 50 | ||
... | @@ -50,5 +57,23 @@ | ... | @@ -50,5 +57,23 @@ |
50 | <div id="view" ng-view></div> | 57 | <div id="view" ng-view></div> |
51 | <foot></foot> | 58 | <foot></foot> |
52 | 59 | ||
60 | +<svg id="icon-defs"> | ||
61 | + <defs> | ||
62 | + <symbol id="checkMark" viewBox="0 0 10 10"> | ||
63 | + <path d="M2.6,4.5c0,0,0.7-0.4,1.2,0.3l1.0,1.8c0,0,2.7-5.4,2.8-5.7c | ||
64 | + 0,0,0.5-0.9,1.4-0.1c0,0,0.5,0.5,0,1.3S6.8,7.3,5.6,9.2c0,0-0.4,0.5 | ||
65 | + -1.2,0.1S2.2,5.4,2.2,5.4S2.2,4.7,2.6,4.5z"></path> | ||
66 | + </symbol> | ||
67 | + <symbol id="xMark" viewBox="0 0 10 10"> | ||
68 | + <path d="M9.0,7.2C8.2,6.9,7.4,6.1,6.7,5.2c0.4-0.5,0.7-0.8,0.8-1.0C | ||
69 | + 7.8,3.5,9.4,1.6,8.1,1.1C6.8,0.6,6.6,1.7,6.6,1.7C6.4,2.1,6.0,2.7, | ||
70 | + 5.4,3.4C4.9,2.5,4.5,1.9,4.5,1.9S3.8,0.2,2.9,0.7C1.9,1.1,2.3,2.3, | ||
71 | + 2.3,2.3c0.3,1.1,0.8,2.1,1.4,2.9C2.5,6.4,1.3,7.4,1.3,7.4S0.8,7.8, | ||
72 | + 0.8,8.1C0.9,8.3,0.9,9.6,2.4,9.1C3.1,8.8,4.1,7.9,5.1,7.0c1.3,1.3, | ||
73 | + 2.5,1.9,2.5,1.9s0.5,0.5,1.4-0.2C9.8,7.9,9.0,7.2,9.0,7.2z"></path> | ||
74 | + </symbol> | ||
75 | + </defs> | ||
76 | +</svg> | ||
77 | + | ||
53 | </body> | 78 | </body> |
54 | </html> | 79 | </html> | ... | ... |
1 | +/** | ||
2 | + * @license AngularJS v1.3.5 | ||
3 | + * (c) 2010-2014 Google, Inc. http://angularjs.org | ||
4 | + * License: MIT | ||
5 | + */ | ||
6 | +(function(window, angular, undefined) {'use strict'; | ||
7 | + | ||
8 | +var $resourceMinErr = angular.$$minErr('$resource'); | ||
9 | + | ||
10 | +// Helper functions and regex to lookup a dotted path on an object | ||
11 | +// stopping at undefined/null. The path must be composed of ASCII | ||
12 | +// identifiers (just like $parse) | ||
13 | +var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/; | ||
14 | + | ||
15 | +function isValidDottedPath(path) { | ||
16 | + return (path != null && path !== '' && path !== 'hasOwnProperty' && | ||
17 | + MEMBER_NAME_REGEX.test('.' + path)); | ||
18 | +} | ||
19 | + | ||
20 | +function lookupDottedPath(obj, path) { | ||
21 | + if (!isValidDottedPath(path)) { | ||
22 | + throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path); | ||
23 | + } | ||
24 | + var keys = path.split('.'); | ||
25 | + for (var i = 0, ii = keys.length; i < ii && obj !== undefined; i++) { | ||
26 | + var key = keys[i]; | ||
27 | + obj = (obj !== null) ? obj[key] : undefined; | ||
28 | + } | ||
29 | + return obj; | ||
30 | +} | ||
31 | + | ||
32 | +/** | ||
33 | + * Create a shallow copy of an object and clear other fields from the destination | ||
34 | + */ | ||
35 | +function shallowClearAndCopy(src, dst) { | ||
36 | + dst = dst || {}; | ||
37 | + | ||
38 | + angular.forEach(dst, function(value, key) { | ||
39 | + delete dst[key]; | ||
40 | + }); | ||
41 | + | ||
42 | + for (var key in src) { | ||
43 | + if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) { | ||
44 | + dst[key] = src[key]; | ||
45 | + } | ||
46 | + } | ||
47 | + | ||
48 | + return dst; | ||
49 | +} | ||
50 | + | ||
51 | +/** | ||
52 | + * @ngdoc module | ||
53 | + * @name ngResource | ||
54 | + * @description | ||
55 | + * | ||
56 | + * # ngResource | ||
57 | + * | ||
58 | + * The `ngResource` module provides interaction support with RESTful services | ||
59 | + * via the $resource service. | ||
60 | + * | ||
61 | + * | ||
62 | + * <div doc-module-components="ngResource"></div> | ||
63 | + * | ||
64 | + * See {@link ngResource.$resource `$resource`} for usage. | ||
65 | + */ | ||
66 | + | ||
67 | +/** | ||
68 | + * @ngdoc service | ||
69 | + * @name $resource | ||
70 | + * @requires $http | ||
71 | + * | ||
72 | + * @description | ||
73 | + * A factory which creates a resource object that lets you interact with | ||
74 | + * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources. | ||
75 | + * | ||
76 | + * The returned resource object has action methods which provide high-level behaviors without | ||
77 | + * the need to interact with the low level {@link ng.$http $http} service. | ||
78 | + * | ||
79 | + * Requires the {@link ngResource `ngResource`} module to be installed. | ||
80 | + * | ||
81 | + * By default, trailing slashes will be stripped from the calculated URLs, | ||
82 | + * which can pose problems with server backends that do not expect that | ||
83 | + * behavior. This can be disabled by configuring the `$resourceProvider` like | ||
84 | + * this: | ||
85 | + * | ||
86 | + * ```js | ||
87 | + app.config(['$resourceProvider', function($resourceProvider) { | ||
88 | + // Don't strip trailing slashes from calculated URLs | ||
89 | + $resourceProvider.defaults.stripTrailingSlashes = false; | ||
90 | + }]); | ||
91 | + * ``` | ||
92 | + * | ||
93 | + * @param {string} url A parametrized URL template with parameters prefixed by `:` as in | ||
94 | + * `/user/:username`. If you are using a URL with a port number (e.g. | ||
95 | + * `http://example.com:8080/api`), it will be respected. | ||
96 | + * | ||
97 | + * If you are using a url with a suffix, just add the suffix, like this: | ||
98 | + * `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')` | ||
99 | + * or even `$resource('http://example.com/resource/:resource_id.:format')` | ||
100 | + * If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be | ||
101 | + * collapsed down to a single `.`. If you need this sequence to appear and not collapse then you | ||
102 | + * can escape it with `/\.`. | ||
103 | + * | ||
104 | + * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in | ||
105 | + * `actions` methods. If any of the parameter value is a function, it will be executed every time | ||
106 | + * when a param value needs to be obtained for a request (unless the param was overridden). | ||
107 | + * | ||
108 | + * Each key value in the parameter object is first bound to url template if present and then any | ||
109 | + * excess keys are appended to the url search query after the `?`. | ||
110 | + * | ||
111 | + * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in | ||
112 | + * URL `/path/greet?salutation=Hello`. | ||
113 | + * | ||
114 | + * If the parameter value is prefixed with `@` then the value for that parameter will be extracted | ||
115 | + * from the corresponding property on the `data` object (provided when calling an action method). For | ||
116 | + * example, if the `defaultParam` object is `{someParam: '@someProp'}` then the value of `someParam` | ||
117 | + * will be `data.someProp`. | ||
118 | + * | ||
119 | + * @param {Object.<Object>=} actions Hash with declaration of custom action that should extend | ||
120 | + * the default set of resource actions. The declaration should be created in the format of {@link | ||
121 | + * ng.$http#usage $http.config}: | ||
122 | + * | ||
123 | + * {action1: {method:?, params:?, isArray:?, headers:?, ...}, | ||
124 | + * action2: {method:?, params:?, isArray:?, headers:?, ...}, | ||
125 | + * ...} | ||
126 | + * | ||
127 | + * Where: | ||
128 | + * | ||
129 | + * - **`action`** – {string} – The name of action. This name becomes the name of the method on | ||
130 | + * your resource object. | ||
131 | + * - **`method`** – {string} – Case insensitive HTTP method (e.g. `GET`, `POST`, `PUT`, | ||
132 | + * `DELETE`, `JSONP`, etc). | ||
133 | + * - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of | ||
134 | + * the parameter value is a function, it will be executed every time when a param value needs to | ||
135 | + * be obtained for a request (unless the param was overridden). | ||
136 | + * - **`url`** – {string} – action specific `url` override. The url templating is supported just | ||
137 | + * like for the resource-level urls. | ||
138 | + * - **`isArray`** – {boolean=} – If true then the returned object for this action is an array, | ||
139 | + * see `returns` section. | ||
140 | + * - **`transformRequest`** – | ||
141 | + * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` – | ||
142 | + * transform function or an array of such functions. The transform function takes the http | ||
143 | + * request body and headers and returns its transformed (typically serialized) version. | ||
144 | + * By default, transformRequest will contain one function that checks if the request data is | ||
145 | + * an object and serializes to using `angular.toJson`. To prevent this behavior, set | ||
146 | + * `transformRequest` to an empty array: `transformRequest: []` | ||
147 | + * - **`transformResponse`** – | ||
148 | + * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` – | ||
149 | + * transform function or an array of such functions. The transform function takes the http | ||
150 | + * response body and headers and returns its transformed (typically deserialized) version. | ||
151 | + * By default, transformResponse will contain one function that checks if the response looks like | ||
152 | + * a JSON string and deserializes it using `angular.fromJson`. To prevent this behavior, set | ||
153 | + * `transformResponse` to an empty array: `transformResponse: []` | ||
154 | + * - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the | ||
155 | + * GET request, otherwise if a cache instance built with | ||
156 | + * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for | ||
157 | + * caching. | ||
158 | + * - **`timeout`** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} that | ||
159 | + * should abort the request when resolved. | ||
160 | + * - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the | ||
161 | + * XHR object. See | ||
162 | + * [requests with credentials](https://developer.mozilla.org/en/http_access_control#section_5) | ||
163 | + * for more information. | ||
164 | + * - **`responseType`** - `{string}` - see | ||
165 | + * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType). | ||
166 | + * - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods - | ||
167 | + * `response` and `responseError`. Both `response` and `responseError` interceptors get called | ||
168 | + * with `http response` object. See {@link ng.$http $http interceptors}. | ||
169 | + * | ||
170 | + * @param {Object} options Hash with custom settings that should extend the | ||
171 | + * default `$resourceProvider` behavior. The only supported option is | ||
172 | + * | ||
173 | + * Where: | ||
174 | + * | ||
175 | + * - **`stripTrailingSlashes`** – {boolean} – If true then the trailing | ||
176 | + * slashes from any calculated URL will be stripped. (Defaults to true.) | ||
177 | + * | ||
178 | + * @returns {Object} A resource "class" object with methods for the default set of resource actions | ||
179 | + * optionally extended with custom `actions`. The default set contains these actions: | ||
180 | + * ```js | ||
181 | + * { 'get': {method:'GET'}, | ||
182 | + * 'save': {method:'POST'}, | ||
183 | + * 'query': {method:'GET', isArray:true}, | ||
184 | + * 'remove': {method:'DELETE'}, | ||
185 | + * 'delete': {method:'DELETE'} }; | ||
186 | + * ``` | ||
187 | + * | ||
188 | + * Calling these methods invoke an {@link ng.$http} with the specified http method, | ||
189 | + * destination and parameters. When the data is returned from the server then the object is an | ||
190 | + * instance of the resource class. The actions `save`, `remove` and `delete` are available on it | ||
191 | + * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create, | ||
192 | + * read, update, delete) on server-side data like this: | ||
193 | + * ```js | ||
194 | + * var User = $resource('/user/:userId', {userId:'@id'}); | ||
195 | + * var user = User.get({userId:123}, function() { | ||
196 | + * user.abc = true; | ||
197 | + * user.$save(); | ||
198 | + * }); | ||
199 | + * ``` | ||
200 | + * | ||
201 | + * It is important to realize that invoking a $resource object method immediately returns an | ||
202 | + * empty reference (object or array depending on `isArray`). Once the data is returned from the | ||
203 | + * server the existing reference is populated with the actual data. This is a useful trick since | ||
204 | + * usually the resource is assigned to a model which is then rendered by the view. Having an empty | ||
205 | + * object results in no rendering, once the data arrives from the server then the object is | ||
206 | + * populated with the data and the view automatically re-renders itself showing the new data. This | ||
207 | + * means that in most cases one never has to write a callback function for the action methods. | ||
208 | + * | ||
209 | + * The action methods on the class object or instance object can be invoked with the following | ||
210 | + * parameters: | ||
211 | + * | ||
212 | + * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])` | ||
213 | + * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])` | ||
214 | + * - non-GET instance actions: `instance.$action([parameters], [success], [error])` | ||
215 | + * | ||
216 | + * Success callback is called with (value, responseHeaders) arguments. Error callback is called | ||
217 | + * with (httpResponse) argument. | ||
218 | + * | ||
219 | + * Class actions return empty instance (with additional properties below). | ||
220 | + * Instance actions return promise of the action. | ||
221 | + * | ||
222 | + * The Resource instances and collection have these additional properties: | ||
223 | + * | ||
224 | + * - `$promise`: the {@link ng.$q promise} of the original server interaction that created this | ||
225 | + * instance or collection. | ||
226 | + * | ||
227 | + * On success, the promise is resolved with the same resource instance or collection object, | ||
228 | + * updated with data from server. This makes it easy to use in | ||
229 | + * {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view | ||
230 | + * rendering until the resource(s) are loaded. | ||
231 | + * | ||
232 | + * On failure, the promise is resolved with the {@link ng.$http http response} object, without | ||
233 | + * the `resource` property. | ||
234 | + * | ||
235 | + * If an interceptor object was provided, the promise will instead be resolved with the value | ||
236 | + * returned by the interceptor. | ||
237 | + * | ||
238 | + * - `$resolved`: `true` after first server interaction is completed (either with success or | ||
239 | + * rejection), `false` before that. Knowing if the Resource has been resolved is useful in | ||
240 | + * data-binding. | ||
241 | + * | ||
242 | + * @example | ||
243 | + * | ||
244 | + * # Credit card resource | ||
245 | + * | ||
246 | + * ```js | ||
247 | + // Define CreditCard class | ||
248 | + var CreditCard = $resource('/user/:userId/card/:cardId', | ||
249 | + {userId:123, cardId:'@id'}, { | ||
250 | + charge: {method:'POST', params:{charge:true}} | ||
251 | + }); | ||
252 | + | ||
253 | + // We can retrieve a collection from the server | ||
254 | + var cards = CreditCard.query(function() { | ||
255 | + // GET: /user/123/card | ||
256 | + // server returns: [ {id:456, number:'1234', name:'Smith'} ]; | ||
257 | + | ||
258 | + var card = cards[0]; | ||
259 | + // each item is an instance of CreditCard | ||
260 | + expect(card instanceof CreditCard).toEqual(true); | ||
261 | + card.name = "J. Smith"; | ||
262 | + // non GET methods are mapped onto the instances | ||
263 | + card.$save(); | ||
264 | + // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'} | ||
265 | + // server returns: {id:456, number:'1234', name: 'J. Smith'}; | ||
266 | + | ||
267 | + // our custom method is mapped as well. | ||
268 | + card.$charge({amount:9.99}); | ||
269 | + // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'} | ||
270 | + }); | ||
271 | + | ||
272 | + // we can create an instance as well | ||
273 | + var newCard = new CreditCard({number:'0123'}); | ||
274 | + newCard.name = "Mike Smith"; | ||
275 | + newCard.$save(); | ||
276 | + // POST: /user/123/card {number:'0123', name:'Mike Smith'} | ||
277 | + // server returns: {id:789, number:'0123', name: 'Mike Smith'}; | ||
278 | + expect(newCard.id).toEqual(789); | ||
279 | + * ``` | ||
280 | + * | ||
281 | + * The object returned from this function execution is a resource "class" which has "static" method | ||
282 | + * for each action in the definition. | ||
283 | + * | ||
284 | + * Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and | ||
285 | + * `headers`. | ||
286 | + * When the data is returned from the server then the object is an instance of the resource type and | ||
287 | + * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD | ||
288 | + * operations (create, read, update, delete) on server-side data. | ||
289 | + | ||
290 | + ```js | ||
291 | + var User = $resource('/user/:userId', {userId:'@id'}); | ||
292 | + User.get({userId:123}, function(user) { | ||
293 | + user.abc = true; | ||
294 | + user.$save(); | ||
295 | + }); | ||
296 | + ``` | ||
297 | + * | ||
298 | + * It's worth noting that the success callback for `get`, `query` and other methods gets passed | ||
299 | + * in the response that came from the server as well as $http header getter function, so one | ||
300 | + * could rewrite the above example and get access to http headers as: | ||
301 | + * | ||
302 | + ```js | ||
303 | + var User = $resource('/user/:userId', {userId:'@id'}); | ||
304 | + User.get({userId:123}, function(u, getResponseHeaders){ | ||
305 | + u.abc = true; | ||
306 | + u.$save(function(u, putResponseHeaders) { | ||
307 | + //u => saved user object | ||
308 | + //putResponseHeaders => $http header getter | ||
309 | + }); | ||
310 | + }); | ||
311 | + ``` | ||
312 | + * | ||
313 | + * You can also access the raw `$http` promise via the `$promise` property on the object returned | ||
314 | + * | ||
315 | + ``` | ||
316 | + var User = $resource('/user/:userId', {userId:'@id'}); | ||
317 | + User.get({userId:123}) | ||
318 | + .$promise.then(function(user) { | ||
319 | + $scope.user = user; | ||
320 | + }); | ||
321 | + ``` | ||
322 | + | ||
323 | + * # Creating a custom 'PUT' request | ||
324 | + * In this example we create a custom method on our resource to make a PUT request | ||
325 | + * ```js | ||
326 | + * var app = angular.module('app', ['ngResource', 'ngRoute']); | ||
327 | + * | ||
328 | + * // Some APIs expect a PUT request in the format URL/object/ID | ||
329 | + * // Here we are creating an 'update' method | ||
330 | + * app.factory('Notes', ['$resource', function($resource) { | ||
331 | + * return $resource('/notes/:id', null, | ||
332 | + * { | ||
333 | + * 'update': { method:'PUT' } | ||
334 | + * }); | ||
335 | + * }]); | ||
336 | + * | ||
337 | + * // In our controller we get the ID from the URL using ngRoute and $routeParams | ||
338 | + * // We pass in $routeParams and our Notes factory along with $scope | ||
339 | + * app.controller('NotesCtrl', ['$scope', '$routeParams', 'Notes', | ||
340 | + function($scope, $routeParams, Notes) { | ||
341 | + * // First get a note object from the factory | ||
342 | + * var note = Notes.get({ id:$routeParams.id }); | ||
343 | + * $id = note.id; | ||
344 | + * | ||
345 | + * // Now call update passing in the ID first then the object you are updating | ||
346 | + * Notes.update({ id:$id }, note); | ||
347 | + * | ||
348 | + * // This will PUT /notes/ID with the note object in the request payload | ||
349 | + * }]); | ||
350 | + * ``` | ||
351 | + */ | ||
352 | +angular.module('ngResource', ['ng']). | ||
353 | + provider('$resource', function() { | ||
354 | + var provider = this; | ||
355 | + | ||
356 | + this.defaults = { | ||
357 | + // Strip slashes by default | ||
358 | + stripTrailingSlashes: true, | ||
359 | + | ||
360 | + // Default actions configuration | ||
361 | + actions: { | ||
362 | + 'get': {method: 'GET'}, | ||
363 | + 'save': {method: 'POST'}, | ||
364 | + 'query': {method: 'GET', isArray: true}, | ||
365 | + 'remove': {method: 'DELETE'}, | ||
366 | + 'delete': {method: 'DELETE'} | ||
367 | + } | ||
368 | + }; | ||
369 | + | ||
370 | + this.$get = ['$http', '$q', function($http, $q) { | ||
371 | + | ||
372 | + var noop = angular.noop, | ||
373 | + forEach = angular.forEach, | ||
374 | + extend = angular.extend, | ||
375 | + copy = angular.copy, | ||
376 | + isFunction = angular.isFunction; | ||
377 | + | ||
378 | + /** | ||
379 | + * We need our custom method because encodeURIComponent is too aggressive and doesn't follow | ||
380 | + * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set | ||
381 | + * (pchar) allowed in path segments: | ||
382 | + * segment = *pchar | ||
383 | + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" | ||
384 | + * pct-encoded = "%" HEXDIG HEXDIG | ||
385 | + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" | ||
386 | + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" | ||
387 | + * / "*" / "+" / "," / ";" / "=" | ||
388 | + */ | ||
389 | + function encodeUriSegment(val) { | ||
390 | + return encodeUriQuery(val, true). | ||
391 | + replace(/%26/gi, '&'). | ||
392 | + replace(/%3D/gi, '='). | ||
393 | + replace(/%2B/gi, '+'); | ||
394 | + } | ||
395 | + | ||
396 | + | ||
397 | + /** | ||
398 | + * This method is intended for encoding *key* or *value* parts of query component. We need a | ||
399 | + * custom method because encodeURIComponent is too aggressive and encodes stuff that doesn't | ||
400 | + * have to be encoded per http://tools.ietf.org/html/rfc3986: | ||
401 | + * query = *( pchar / "/" / "?" ) | ||
402 | + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" | ||
403 | + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" | ||
404 | + * pct-encoded = "%" HEXDIG HEXDIG | ||
405 | + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" | ||
406 | + * / "*" / "+" / "," / ";" / "=" | ||
407 | + */ | ||
408 | + function encodeUriQuery(val, pctEncodeSpaces) { | ||
409 | + return encodeURIComponent(val). | ||
410 | + replace(/%40/gi, '@'). | ||
411 | + replace(/%3A/gi, ':'). | ||
412 | + replace(/%24/g, '$'). | ||
413 | + replace(/%2C/gi, ','). | ||
414 | + replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); | ||
415 | + } | ||
416 | + | ||
417 | + function Route(template, defaults) { | ||
418 | + this.template = template; | ||
419 | + this.defaults = extend({}, provider.defaults, defaults); | ||
420 | + this.urlParams = {}; | ||
421 | + } | ||
422 | + | ||
423 | + Route.prototype = { | ||
424 | + setUrlParams: function(config, params, actionUrl) { | ||
425 | + var self = this, | ||
426 | + url = actionUrl || self.template, | ||
427 | + val, | ||
428 | + encodedVal; | ||
429 | + | ||
430 | + var urlParams = self.urlParams = {}; | ||
431 | + forEach(url.split(/\W/), function(param) { | ||
432 | + if (param === 'hasOwnProperty') { | ||
433 | + throw $resourceMinErr('badname', "hasOwnProperty is not a valid parameter name."); | ||
434 | + } | ||
435 | + if (!(new RegExp("^\\d+$").test(param)) && param && | ||
436 | + (new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) { | ||
437 | + urlParams[param] = true; | ||
438 | + } | ||
439 | + }); | ||
440 | + url = url.replace(/\\:/g, ':'); | ||
441 | + | ||
442 | + params = params || {}; | ||
443 | + forEach(self.urlParams, function(_, urlParam) { | ||
444 | + val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam]; | ||
445 | + if (angular.isDefined(val) && val !== null) { | ||
446 | + encodedVal = encodeUriSegment(val); | ||
447 | + url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), function(match, p1) { | ||
448 | + return encodedVal + p1; | ||
449 | + }); | ||
450 | + } else { | ||
451 | + url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match, | ||
452 | + leadingSlashes, tail) { | ||
453 | + if (tail.charAt(0) == '/') { | ||
454 | + return tail; | ||
455 | + } else { | ||
456 | + return leadingSlashes + tail; | ||
457 | + } | ||
458 | + }); | ||
459 | + } | ||
460 | + }); | ||
461 | + | ||
462 | + // strip trailing slashes and set the url (unless this behavior is specifically disabled) | ||
463 | + if (self.defaults.stripTrailingSlashes) { | ||
464 | + url = url.replace(/\/+$/, '') || '/'; | ||
465 | + } | ||
466 | + | ||
467 | + // then replace collapse `/.` if found in the last URL path segment before the query | ||
468 | + // E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x` | ||
469 | + url = url.replace(/\/\.(?=\w+($|\?))/, '.'); | ||
470 | + // replace escaped `/\.` with `/.` | ||
471 | + config.url = url.replace(/\/\\\./, '/.'); | ||
472 | + | ||
473 | + | ||
474 | + // set params - delegate param encoding to $http | ||
475 | + forEach(params, function(value, key) { | ||
476 | + if (!self.urlParams[key]) { | ||
477 | + config.params = config.params || {}; | ||
478 | + config.params[key] = value; | ||
479 | + } | ||
480 | + }); | ||
481 | + } | ||
482 | + }; | ||
483 | + | ||
484 | + | ||
485 | + function resourceFactory(url, paramDefaults, actions, options) { | ||
486 | + var route = new Route(url, options); | ||
487 | + | ||
488 | + actions = extend({}, provider.defaults.actions, actions); | ||
489 | + | ||
490 | + function extractParams(data, actionParams) { | ||
491 | + var ids = {}; | ||
492 | + actionParams = extend({}, paramDefaults, actionParams); | ||
493 | + forEach(actionParams, function(value, key) { | ||
494 | + if (isFunction(value)) { value = value(); } | ||
495 | + ids[key] = value && value.charAt && value.charAt(0) == '@' ? | ||
496 | + lookupDottedPath(data, value.substr(1)) : value; | ||
497 | + }); | ||
498 | + return ids; | ||
499 | + } | ||
500 | + | ||
501 | + function defaultResponseInterceptor(response) { | ||
502 | + return response.resource; | ||
503 | + } | ||
504 | + | ||
505 | + function Resource(value) { | ||
506 | + shallowClearAndCopy(value || {}, this); | ||
507 | + } | ||
508 | + | ||
509 | + Resource.prototype.toJSON = function() { | ||
510 | + var data = extend({}, this); | ||
511 | + delete data.$promise; | ||
512 | + delete data.$resolved; | ||
513 | + return data; | ||
514 | + }; | ||
515 | + | ||
516 | + forEach(actions, function(action, name) { | ||
517 | + var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method); | ||
518 | + | ||
519 | + Resource[name] = function(a1, a2, a3, a4) { | ||
520 | + var params = {}, data, success, error; | ||
521 | + | ||
522 | + /* jshint -W086 */ /* (purposefully fall through case statements) */ | ||
523 | + switch (arguments.length) { | ||
524 | + case 4: | ||
525 | + error = a4; | ||
526 | + success = a3; | ||
527 | + //fallthrough | ||
528 | + case 3: | ||
529 | + case 2: | ||
530 | + if (isFunction(a2)) { | ||
531 | + if (isFunction(a1)) { | ||
532 | + success = a1; | ||
533 | + error = a2; | ||
534 | + break; | ||
535 | + } | ||
536 | + | ||
537 | + success = a2; | ||
538 | + error = a3; | ||
539 | + //fallthrough | ||
540 | + } else { | ||
541 | + params = a1; | ||
542 | + data = a2; | ||
543 | + success = a3; | ||
544 | + break; | ||
545 | + } | ||
546 | + case 1: | ||
547 | + if (isFunction(a1)) success = a1; | ||
548 | + else if (hasBody) data = a1; | ||
549 | + else params = a1; | ||
550 | + break; | ||
551 | + case 0: break; | ||
552 | + default: | ||
553 | + throw $resourceMinErr('badargs', | ||
554 | + "Expected up to 4 arguments [params, data, success, error], got {0} arguments", | ||
555 | + arguments.length); | ||
556 | + } | ||
557 | + /* jshint +W086 */ /* (purposefully fall through case statements) */ | ||
558 | + | ||
559 | + var isInstanceCall = this instanceof Resource; | ||
560 | + var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data)); | ||
561 | + var httpConfig = {}; | ||
562 | + var responseInterceptor = action.interceptor && action.interceptor.response || | ||
563 | + defaultResponseInterceptor; | ||
564 | + var responseErrorInterceptor = action.interceptor && action.interceptor.responseError || | ||
565 | + undefined; | ||
566 | + | ||
567 | + forEach(action, function(value, key) { | ||
568 | + if (key != 'params' && key != 'isArray' && key != 'interceptor') { | ||
569 | + httpConfig[key] = copy(value); | ||
570 | + } | ||
571 | + }); | ||
572 | + | ||
573 | + if (hasBody) httpConfig.data = data; | ||
574 | + route.setUrlParams(httpConfig, | ||
575 | + extend({}, extractParams(data, action.params || {}), params), | ||
576 | + action.url); | ||
577 | + | ||
578 | + var promise = $http(httpConfig).then(function(response) { | ||
579 | + var data = response.data, | ||
580 | + promise = value.$promise; | ||
581 | + | ||
582 | + if (data) { | ||
583 | + // Need to convert action.isArray to boolean in case it is undefined | ||
584 | + // jshint -W018 | ||
585 | + if (angular.isArray(data) !== (!!action.isArray)) { | ||
586 | + throw $resourceMinErr('badcfg', | ||
587 | + 'Error in resource configuration for action `{0}`. Expected response to ' + | ||
588 | + 'contain an {1} but got an {2}', name, action.isArray ? 'array' : 'object', | ||
589 | + angular.isArray(data) ? 'array' : 'object'); | ||
590 | + } | ||
591 | + // jshint +W018 | ||
592 | + if (action.isArray) { | ||
593 | + value.length = 0; | ||
594 | + forEach(data, function(item) { | ||
595 | + if (typeof item === "object") { | ||
596 | + value.push(new Resource(item)); | ||
597 | + } else { | ||
598 | + // Valid JSON values may be string literals, and these should not be converted | ||
599 | + // into objects. These items will not have access to the Resource prototype | ||
600 | + // methods, but unfortunately there | ||
601 | + value.push(item); | ||
602 | + } | ||
603 | + }); | ||
604 | + } else { | ||
605 | + shallowClearAndCopy(data, value); | ||
606 | + value.$promise = promise; | ||
607 | + } | ||
608 | + } | ||
609 | + | ||
610 | + value.$resolved = true; | ||
611 | + | ||
612 | + response.resource = value; | ||
613 | + | ||
614 | + return response; | ||
615 | + }, function(response) { | ||
616 | + value.$resolved = true; | ||
617 | + | ||
618 | + (error || noop)(response); | ||
619 | + | ||
620 | + return $q.reject(response); | ||
621 | + }); | ||
622 | + | ||
623 | + promise = promise.then( | ||
624 | + function(response) { | ||
625 | + var value = responseInterceptor(response); | ||
626 | + (success || noop)(value, response.headers); | ||
627 | + return value; | ||
628 | + }, | ||
629 | + responseErrorInterceptor); | ||
630 | + | ||
631 | + if (!isInstanceCall) { | ||
632 | + // we are creating instance / collection | ||
633 | + // - set the initial promise | ||
634 | + // - return the instance / collection | ||
635 | + value.$promise = promise; | ||
636 | + value.$resolved = false; | ||
637 | + | ||
638 | + return value; | ||
639 | + } | ||
640 | + | ||
641 | + // instance call | ||
642 | + return promise; | ||
643 | + }; | ||
644 | + | ||
645 | + | ||
646 | + Resource.prototype['$' + name] = function(params, success, error) { | ||
647 | + if (isFunction(params)) { | ||
648 | + error = success; success = params; params = {}; | ||
649 | + } | ||
650 | + var result = Resource[name].call(this, params, this, success, error); | ||
651 | + return result.$promise || result; | ||
652 | + }; | ||
653 | + }); | ||
654 | + | ||
655 | + Resource.bind = function(additionalParamDefaults) { | ||
656 | + return resourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions); | ||
657 | + }; | ||
658 | + | ||
659 | + return Resource; | ||
660 | + } | ||
661 | + | ||
662 | + return resourceFactory; | ||
663 | + }]; | ||
664 | + }); | ||
665 | + | ||
666 | + | ||
667 | +})(window, window.angular); |
-
Please register or login to post a comment