angularjs-rails-resource 1.0.0.pre.4 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,167 +0,0 @@
1
- /**
2
- * @license AngularJS v1.1.4
3
- * (c) 2010-2012 Google, Inc. http://angularjs.org
4
- * License: MIT
5
- */
6
- (function(window, angular, undefined) {
7
- 'use strict';
8
-
9
- var directive = {};
10
-
11
- directive.dropdownToggle =
12
- ['$document', '$location', '$window',
13
- function ($document, $location, $window) {
14
- var openElement = null, close;
15
- return {
16
- restrict: 'C',
17
- link: function(scope, element, attrs) {
18
- scope.$watch(function dropdownTogglePathWatch(){return $location.path();}, function dropdownTogglePathWatchAction() {
19
- close && close();
20
- });
21
-
22
- element.parent().bind('click', function(event) {
23
- close && close();
24
- });
25
-
26
- element.bind('click', function(event) {
27
- event.preventDefault();
28
- event.stopPropagation();
29
-
30
- var iWasOpen = false;
31
-
32
- if (openElement) {
33
- iWasOpen = openElement === element;
34
- close();
35
- }
36
-
37
- if (!iWasOpen){
38
- element.parent().addClass('open');
39
- openElement = element;
40
-
41
- close = function (event) {
42
- event && event.preventDefault();
43
- event && event.stopPropagation();
44
- $document.unbind('click', close);
45
- element.parent().removeClass('open');
46
- close = null;
47
- openElement = null;
48
- }
49
-
50
- $document.bind('click', close);
51
- }
52
- });
53
- }
54
- };
55
- }];
56
-
57
-
58
- directive.tabbable = function() {
59
- return {
60
- restrict: 'C',
61
- compile: function(element) {
62
- var navTabs = angular.element('<ul class="nav nav-tabs"></ul>'),
63
- tabContent = angular.element('<div class="tab-content"></div>');
64
-
65
- tabContent.append(element.contents());
66
- element.append(navTabs).append(tabContent);
67
- },
68
- controller: ['$scope', '$element', function($scope, $element) {
69
- var navTabs = $element.contents().eq(0),
70
- ngModel = $element.controller('ngModel') || {},
71
- tabs = [],
72
- selectedTab;
73
-
74
- ngModel.$render = function() {
75
- var $viewValue = this.$viewValue;
76
-
77
- if (selectedTab ? (selectedTab.value != $viewValue) : $viewValue) {
78
- if(selectedTab) {
79
- selectedTab.paneElement.removeClass('active');
80
- selectedTab.tabElement.removeClass('active');
81
- selectedTab = null;
82
- }
83
- if($viewValue) {
84
- for(var i = 0, ii = tabs.length; i < ii; i++) {
85
- if ($viewValue == tabs[i].value) {
86
- selectedTab = tabs[i];
87
- break;
88
- }
89
- }
90
- if (selectedTab) {
91
- selectedTab.paneElement.addClass('active');
92
- selectedTab.tabElement.addClass('active');
93
- }
94
- }
95
-
96
- }
97
- };
98
-
99
- this.addPane = function(element, attr) {
100
- var li = angular.element('<li><a href></a></li>'),
101
- a = li.find('a'),
102
- tab = {
103
- paneElement: element,
104
- paneAttrs: attr,
105
- tabElement: li
106
- };
107
-
108
- tabs.push(tab);
109
-
110
- attr.$observe('value', update)();
111
- attr.$observe('title', function(){ update(); a.text(tab.title); })();
112
-
113
- function update() {
114
- tab.title = attr.title;
115
- tab.value = attr.value || attr.title;
116
- if (!ngModel.$setViewValue && (!ngModel.$viewValue || tab == selectedTab)) {
117
- // we are not part of angular
118
- ngModel.$viewValue = tab.value;
119
- }
120
- ngModel.$render();
121
- }
122
-
123
- navTabs.append(li);
124
- li.bind('click', function(event) {
125
- event.preventDefault();
126
- event.stopPropagation();
127
- if (ngModel.$setViewValue) {
128
- $scope.$apply(function() {
129
- ngModel.$setViewValue(tab.value);
130
- ngModel.$render();
131
- });
132
- } else {
133
- // we are not part of angular
134
- ngModel.$viewValue = tab.value;
135
- ngModel.$render();
136
- }
137
- });
138
-
139
- return function() {
140
- tab.tabElement.remove();
141
- for(var i = 0, ii = tabs.length; i < ii; i++ ) {
142
- if (tab == tabs[i]) {
143
- tabs.splice(i, 1);
144
- }
145
- }
146
- };
147
- }
148
- }]
149
- };
150
- };
151
-
152
-
153
- directive.tabPane = function() {
154
- return {
155
- require: '^tabbable',
156
- restrict: 'C',
157
- link: function(scope, element, attrs, tabsCtrl) {
158
- element.bind('$remove', tabsCtrl.addPane(element, attrs));
159
- }
160
- };
161
- };
162
-
163
-
164
- angular.module('bootstrap', []).directive(directive);
165
-
166
-
167
- })(window, window.angular);
@@ -1,4 +0,0 @@
1
- angular.module("ngLocale", [], ["$provide", function($provide) {
2
- var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"};
3
- $provide.value("$locale", {"DATETIME_FORMATS":{"MONTH":["January","February","March","April","May","June","July","August","September","October","November","December"],"SHORTMONTH":["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],"DAY":["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],"SHORTDAY":["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],"AMPMS":["AM","PM"],"medium":"MMM d, y h:mm:ss a","short":"M/d/yy h:mm a","fullDate":"EEEE, MMMM d, y","longDate":"MMMM d, y","mediumDate":"MMM d, y","shortDate":"M/d/yy","mediumTime":"h:mm:ss a","shortTime":"h:mm a"},"NUMBER_FORMATS":{"DECIMAL_SEP":".","GROUP_SEP":",","PATTERNS":[{"minInt":1,"minFrac":0,"macFrac":0,"posPre":"","posSuf":"","negPre":"-","negSuf":"","gSize":3,"lgSize":3,"maxFrac":3},{"minInt":1,"minFrac":2,"macFrac":0,"posPre":"\u00A4","posSuf":"","negPre":"(\u00A4","negSuf":")","gSize":3,"lgSize":3,"maxFrac":2}],"CURRENCY_SYM":"$"},"pluralCat":function (n) { if (n == 1) { return PLURAL_CATEGORY.ONE; } return PLURAL_CATEGORY.OTHER;},"id":"en-us"});
4
- }]);
@@ -1,267 +0,0 @@
1
- /**
2
- * @license AngularJS v1.1.4
3
- * (c) 2010-2012 Google, Inc. http://angularjs.org
4
- * License: MIT
5
- */
6
- (function(window, angular, undefined) {
7
- 'use strict';
8
-
9
- /**
10
- * @ngdoc overview
11
- * @name ngMobile
12
- * @description
13
- */
14
-
15
- /*
16
- * Touch events and other mobile helpers by Braden Shepherdson (braden.shepherdson@gmail.com)
17
- * Based on jQuery Mobile touch event handling (jquerymobile.com)
18
- */
19
-
20
- // define ngSanitize module and register $sanitize service
21
- var ngMobile = angular.module('ngMobile', []);
22
-
23
- /**
24
- * @ngdoc directive
25
- * @name ngMobile.directive:ngTap
26
- *
27
- * @description
28
- * Specify custom behavior when element is tapped on a touchscreen device.
29
- * A tap is a brief, down-and-up touch without much motion.
30
- *
31
- * @element ANY
32
- * @param {expression} ngClick {@link guide/expression Expression} to evaluate
33
- * upon tap. (Event object is available as `$event`)
34
- *
35
- * @example
36
- <doc:example>
37
- <doc:source>
38
- <button ng-tap="count = count + 1" ng-init="count=0">
39
- Increment
40
- </button>
41
- count: {{ count }}
42
- </doc:source>
43
- </doc:example>
44
- */
45
-
46
- ngMobile.config(['$provide', function($provide) {
47
- $provide.decorator('ngClickDirective', ['$delegate', function($delegate) {
48
- // drop the default ngClick directive
49
- $delegate.shift();
50
- return $delegate;
51
- }]);
52
- }]);
53
-
54
- ngMobile.directive('ngClick', ['$parse', '$timeout', '$rootElement',
55
- function($parse, $timeout, $rootElement) {
56
- var TAP_DURATION = 750; // Shorter than 750ms is a tap, longer is a taphold or drag.
57
- var MOVE_TOLERANCE = 12; // 12px seems to work in most mobile browsers.
58
- var PREVENT_DURATION = 2500; // 2.5 seconds maximum from preventGhostClick call to click
59
- var CLICKBUSTER_THRESHOLD = 25; // 25 pixels in any dimension is the limit for busting clicks.
60
- var lastPreventedTime;
61
- var touchCoordinates;
62
-
63
-
64
- // TAP EVENTS AND GHOST CLICKS
65
- //
66
- // Why tap events?
67
- // Mobile browsers detect a tap, then wait a moment (usually ~300ms) to see if you're
68
- // double-tapping, and then fire a click event.
69
- //
70
- // This delay sucks and makes mobile apps feel unresponsive.
71
- // So we detect touchstart, touchmove, touchcancel and touchend ourselves and determine when
72
- // the user has tapped on something.
73
- //
74
- // What happens when the browser then generates a click event?
75
- // The browser, of course, also detects the tap and fires a click after a delay. This results in
76
- // tapping/clicking twice. So we do "clickbusting" to prevent it.
77
- //
78
- // How does it work?
79
- // We attach global touchstart and click handlers, that run during the capture (early) phase.
80
- // So the sequence for a tap is:
81
- // - global touchstart: Sets an "allowable region" at the point touched.
82
- // - element's touchstart: Starts a touch
83
- // (- touchmove or touchcancel ends the touch, no click follows)
84
- // - element's touchend: Determines if the tap is valid (didn't move too far away, didn't hold
85
- // too long) and fires the user's tap handler. The touchend also calls preventGhostClick().
86
- // - preventGhostClick() removes the allowable region the global touchstart created.
87
- // - The browser generates a click event.
88
- // - The global click handler catches the click, and checks whether it was in an allowable region.
89
- // - If preventGhostClick was called, the region will have been removed, the click is busted.
90
- // - If the region is still there, the click proceeds normally. Therefore clicks on links and
91
- // other elements without ngTap on them work normally.
92
- //
93
- // This is an ugly, terrible hack!
94
- // Yeah, tell me about it. The alternatives are using the slow click events, or making our users
95
- // deal with the ghost clicks, so I consider this the least of evils. Fortunately Angular
96
- // encapsulates this ugly logic away from the user.
97
- //
98
- // Why not just put click handlers on the element?
99
- // We do that too, just to be sure. The problem is that the tap event might have caused the DOM
100
- // to change, so that the click fires in the same position but something else is there now. So
101
- // the handlers are global and care only about coordinates and not elements.
102
-
103
- // Checks if the coordinates are close enough to be within the region.
104
- function hit(x1, y1, x2, y2) {
105
- return Math.abs(x1 - x2) < CLICKBUSTER_THRESHOLD && Math.abs(y1 - y2) < CLICKBUSTER_THRESHOLD;
106
- }
107
-
108
- // Checks a list of allowable regions against a click location.
109
- // Returns true if the click should be allowed.
110
- // Splices out the allowable region from the list after it has been used.
111
- function checkAllowableRegions(touchCoordinates, x, y) {
112
- for (var i = 0; i < touchCoordinates.length; i += 2) {
113
- if (hit(touchCoordinates[i], touchCoordinates[i+1], x, y)) {
114
- touchCoordinates.splice(i, i + 2);
115
- return true; // allowable region
116
- }
117
- }
118
- return false; // No allowable region; bust it.
119
- }
120
-
121
- // Global click handler that prevents the click if it's in a bustable zone and preventGhostClick
122
- // was called recently.
123
- function onClick(event) {
124
- if (Date.now() - lastPreventedTime > PREVENT_DURATION) {
125
- return; // Too old.
126
- }
127
-
128
- var touches = event.touches && event.touches.length ? event.touches : [event];
129
- var x = touches[0].clientX;
130
- var y = touches[0].clientY;
131
- // Work around desktop Webkit quirk where clicking a label will fire two clicks (on the label
132
- // and on the input element). Depending on the exact browser, this second click we don't want
133
- // to bust has either (0,0) or negative coordinates.
134
- if (x < 1 && y < 1) {
135
- return; // offscreen
136
- }
137
-
138
- // Look for an allowable region containing this click.
139
- // If we find one, that means it was created by touchstart and not removed by
140
- // preventGhostClick, so we don't bust it.
141
- if (checkAllowableRegions(touchCoordinates, x, y)) {
142
- return;
143
- }
144
-
145
- // If we didn't find an allowable region, bust the click.
146
- event.stopPropagation();
147
- event.preventDefault();
148
- }
149
-
150
-
151
- // Global touchstart handler that creates an allowable region for a click event.
152
- // This allowable region can be removed by preventGhostClick if we want to bust it.
153
- function onTouchStart(event) {
154
- var touches = event.touches && event.touches.length ? event.touches : [event];
155
- var x = touches[0].clientX;
156
- var y = touches[0].clientY;
157
- touchCoordinates.push(x, y);
158
-
159
- $timeout(function() {
160
- // Remove the allowable region.
161
- for (var i = 0; i < touchCoordinates.length; i += 2) {
162
- if (touchCoordinates[i] == x && touchCoordinates[i+1] == y) {
163
- touchCoordinates.splice(i, i + 2);
164
- return;
165
- }
166
- }
167
- }, PREVENT_DURATION, false);
168
- }
169
-
170
- // On the first call, attaches some event handlers. Then whenever it gets called, it creates a
171
- // zone around the touchstart where clicks will get busted.
172
- function preventGhostClick(x, y) {
173
- if (!touchCoordinates) {
174
- $rootElement[0].addEventListener('click', onClick, true);
175
- $rootElement[0].addEventListener('touchstart', onTouchStart, true);
176
- touchCoordinates = [];
177
- }
178
-
179
- lastPreventedTime = Date.now();
180
-
181
- checkAllowableRegions(touchCoordinates, x, y);
182
- }
183
-
184
- // Actual linking function.
185
- return function(scope, element, attr) {
186
- var expressionFn = $parse(attr.ngClick),
187
- tapping = false,
188
- tapElement, // Used to blur the element after a tap.
189
- startTime, // Used to check if the tap was held too long.
190
- touchStartX,
191
- touchStartY;
192
-
193
- function resetState() {
194
- tapping = false;
195
- }
196
-
197
- element.bind('touchstart', function(event) {
198
- tapping = true;
199
- tapElement = event.target ? event.target : event.srcElement; // IE uses srcElement.
200
- // Hack for Safari, which can target text nodes instead of containers.
201
- if(tapElement.nodeType == 3) {
202
- tapElement = tapElement.parentNode;
203
- }
204
-
205
- startTime = Date.now();
206
-
207
- var touches = event.touches && event.touches.length ? event.touches : [event];
208
- var e = touches[0].originalEvent || touches[0];
209
- touchStartX = e.clientX;
210
- touchStartY = e.clientY;
211
- });
212
-
213
- element.bind('touchmove', function(event) {
214
- resetState();
215
- });
216
-
217
- element.bind('touchcancel', function(event) {
218
- resetState();
219
- });
220
-
221
- element.bind('touchend', function(event) {
222
- var diff = Date.now() - startTime;
223
-
224
- var touches = (event.changedTouches && event.changedTouches.length) ? event.changedTouches :
225
- ((event.touches && event.touches.length) ? event.touches : [event]);
226
- var e = touches[0].originalEvent || touches[0];
227
- var x = e.clientX;
228
- var y = e.clientY;
229
- var dist = Math.sqrt( Math.pow(x - touchStartX, 2) + Math.pow(y - touchStartY, 2) );
230
-
231
- if (tapping && diff < TAP_DURATION && dist < MOVE_TOLERANCE) {
232
- // Call preventGhostClick so the clickbuster will catch the corresponding click.
233
- preventGhostClick(x, y);
234
-
235
- // Blur the focused element (the button, probably) before firing the callback.
236
- // This doesn't work perfectly on Android Chrome, but seems to work elsewhere.
237
- // I couldn't get anything to work reliably on Android Chrome.
238
- if (tapElement) {
239
- tapElement.blur();
240
- }
241
-
242
- scope.$apply(function() {
243
- // TODO(braden): This is sending the touchend, not a tap or click. Is that kosher?
244
- expressionFn(scope, {$event: event});
245
- });
246
- }
247
- tapping = false;
248
- });
249
-
250
- // Hack for iOS Safari's benefit. It goes searching for onclick handlers and is liable to click
251
- // something else nearby.
252
- element.onclick = function(event) { };
253
-
254
- // Fallback click handler.
255
- // Busted clicks don't get this far, and adding this handler allows ng-tap to be used on
256
- // desktop as well, to allow more portable sites.
257
- element.bind('click', function(event) {
258
- scope.$apply(function() {
259
- expressionFn(scope, {$event: event});
260
- });
261
- });
262
- };
263
- }]);
264
-
265
-
266
-
267
- })(window, window.angular);
@@ -1,521 +0,0 @@
1
- /**
2
- * @license AngularJS v1.1.4
3
- * (c) 2010-2012 Google, Inc. http://angularjs.org
4
- * License: MIT
5
- */
6
- (function(window, angular, undefined) {
7
- 'use strict';
8
-
9
- /**
10
- * @ngdoc overview
11
- * @name ngResource
12
- * @description
13
- */
14
-
15
- /**
16
- * @ngdoc object
17
- * @name ngResource.$resource
18
- * @requires $http
19
- *
20
- * @description
21
- * A factory which creates a resource object that lets you interact with
22
- * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources.
23
- *
24
- * The returned resource object has action methods which provide high-level behaviors without
25
- * the need to interact with the low level {@link ng.$http $http} service.
26
- *
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')`.
40
- *
41
- * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
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).
44
- *
45
- * Each key value in the parameter object is first bound to url template if present and then any
46
- * excess keys are appended to the url search query after the `?`.
47
- *
48
- * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in
49
- * URL `/path/greet?salutation=Hello`.
50
- *
51
- * If the parameter value is prefixed with `@` then the value of that parameter is extracted from
52
- * the data object (useful for non-GET operations).
53
- *
54
- * @param {Object.<Object>=} actions Hash with declaration of custom action that should extend the
55
- * default set of resource actions. The declaration should be created in the format of {@link
56
- * ng.$http#Parameters $http.config}:
57
- *
58
- * {action1: {method:?, params:?, isArray:?, headers:?, ...},
59
- * action2: {method:?, params:?, isArray:?, headers:?, ...},
60
- * ...}
61
- *
62
- * Where:
63
- *
64
- * - **`action`** – {string} – The name of action. This name becomes the name of the method on your
65
- * resource object.
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
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}.
91
- *
92
- * @returns {Object} A resource "class" object with methods for the default set of resource actions
93
- * optionally extended with custom `actions`. The default set contains these actions:
94
- *
95
- * { 'get': {method:'GET'},
96
- * 'save': {method:'POST'},
97
- * 'query': {method:'GET', isArray:true},
98
- * 'remove': {method:'DELETE'},
99
- * 'delete': {method:'DELETE'} };
100
- *
101
- * Calling these methods invoke an {@link ng.$http} with the specified http method,
102
- * destination and parameters. When the data is returned from the server then the object is an
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:
106
- * <pre>
107
- var User = $resource('/user/:userId', {userId:'@id'});
108
- var user = User.get({userId:123}, function() {
109
- user.abc = true;
110
- user.$save();
111
- });
112
- </pre>
113
- *
114
- * It is important to realize that invoking a $resource object method immediately returns an
115
- * empty reference (object or array depending on `isArray`). Once the data is returned from the
116
- * server the existing reference is populated with the actual data. This is a useful trick since
117
- * usually the resource is assigned to a model which is then rendered by the view. Having an empty
118
- * object results in no rendering, once the data arrives from the server then the object is
119
- * populated with the data and the view automatically re-renders itself showing the new data. This
120
- * means that in most case one never has to write a callback function for the action methods.
121
- *
122
- * The action methods on the class object or instance object can be invoked with the following
123
- * parameters:
124
- *
125
- * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])`
126
- * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])`
127
- * - non-GET instance actions: `instance.$action([parameters], [success], [error])`
128
- *
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
- *
148
- * @example
149
- *
150
- * # Credit card resource
151
- *
152
- * <pre>
153
- // Define CreditCard class
154
- var CreditCard = $resource('/user/:userId/card/:cardId',
155
- {userId:123, cardId:'@id'}, {
156
- charge: {method:'POST', params:{charge:true}}
157
- });
158
-
159
- // We can retrieve a collection from the server
160
- var cards = CreditCard.query(function() {
161
- // GET: /user/123/card
162
- // server returns: [ {id:456, number:'1234', name:'Smith'} ];
163
-
164
- var card = cards[0];
165
- // each item is an instance of CreditCard
166
- expect(card instanceof CreditCard).toEqual(true);
167
- card.name = "J. Smith";
168
- // non GET methods are mapped onto the instances
169
- card.$save();
170
- // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
171
- // server returns: {id:456, number:'1234', name: 'J. Smith'};
172
-
173
- // our custom method is mapped as well.
174
- card.$charge({amount:9.99});
175
- // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
176
- });
177
-
178
- // we can create an instance as well
179
- var newCard = new CreditCard({number:'0123'});
180
- newCard.name = "Mike Smith";
181
- newCard.$save();
182
- // POST: /user/123/card {number:'0123', name:'Mike Smith'}
183
- // server returns: {id:789, number:'01234', name: 'Mike Smith'};
184
- expect(newCard.id).toEqual(789);
185
- * </pre>
186
- *
187
- * The object returned from this function execution is a resource "class" which has "static" method
188
- * for each action in the definition.
189
- *
190
- * Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and `headers`.
191
- * When the data is returned from the server then the object is an instance of the resource type and
192
- * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD
193
- * operations (create, read, update, delete) on server-side data.
194
-
195
- <pre>
196
- var User = $resource('/user/:userId', {userId:'@id'});
197
- var user = User.get({userId:123}, function() {
198
- user.abc = true;
199
- user.$save();
200
- });
201
- </pre>
202
- *
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:
206
- *
207
- <pre>
208
- var User = $resource('/user/:userId', {userId:'@id'});
209
- User.get({userId:123}, function(u, getResponseHeaders){
210
- u.abc = true;
211
- u.$save(function(u, putResponseHeaders) {
212
- //u => saved user object
213
- //putResponseHeaders => $http header getter
214
- });
215
- });
216
- </pre>
217
-
218
- * # Buzz client
219
-
220
- Let's look at what a buzz client created with the `$resource` service looks like:
221
- <doc:example>
222
- <doc:source jsfiddle="false">
223
- <script>
224
- function BuzzController($resource) {
225
- this.userId = 'googlebuzz';
226
- this.Activity = $resource(
227
- 'https://www.googleapis.com/buzz/v1/activities/:userId/:visibility/:activityId/:comments',
228
- {alt:'json', callback:'JSON_CALLBACK'},
229
- {get:{method:'JSONP', params:{visibility:'@self'}}, replies: {method:'JSONP', params:{visibility:'@self', comments:'@comments'}}}
230
- );
231
- }
232
-
233
- BuzzController.prototype = {
234
- fetch: function() {
235
- this.activities = this.Activity.get({userId:this.userId});
236
- },
237
- expandReplies: function(activity) {
238
- activity.replies = this.Activity.replies({userId:this.userId, activityId:activity.id});
239
- }
240
- };
241
- BuzzController.$inject = ['$resource'];
242
- </script>
243
-
244
- <div ng-controller="BuzzController">
245
- <input ng-model="userId"/>
246
- <button ng-click="fetch()">fetch</button>
247
- <hr/>
248
- <div ng-repeat="item in activities.data.items">
249
- <h1 style="font-size: 15px;">
250
- <img src="{{item.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
251
- <a href="{{item.actor.profileUrl}}">{{item.actor.name}}</a>
252
- <a href ng-click="expandReplies(item)" style="float: right;">Expand replies: {{item.links.replies[0].count}}</a>
253
- </h1>
254
- {{item.object.content | html}}
255
- <div ng-repeat="reply in item.replies.data.items" style="margin-left: 20px;">
256
- <img src="{{reply.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
257
- <a href="{{reply.actor.profileUrl}}">{{reply.actor.name}}</a>: {{reply.content | html}}
258
- </div>
259
- </div>
260
- </div>
261
- </doc:source>
262
- <doc:scenario>
263
- </doc:scenario>
264
- </doc:example>
265
- */
266
- angular.module('ngResource', ['ng']).
267
- factory('$resource', ['$http', '$parse', function($http, $parse) {
268
- var DEFAULT_ACTIONS = {
269
- 'get': {method:'GET'},
270
- 'save': {method:'POST'},
271
- 'query': {method:'GET', isArray:true},
272
- 'remove': {method:'DELETE'},
273
- 'delete': {method:'DELETE'}
274
- };
275
- var noop = angular.noop,
276
- forEach = angular.forEach,
277
- extend = angular.extend,
278
- copy = angular.copy,
279
- isFunction = angular.isFunction,
280
- getter = function(obj, path) {
281
- return $parse(path)(obj);
282
- };
283
-
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) {
324
- this.template = template = template + '#';
325
- this.defaults = defaults || {};
326
- this.urlParams = {};
327
- }
328
-
329
- Route.prototype = {
330
- setUrlParams: function(config, params, actionUrl) {
331
- var self = this,
332
- url = actionUrl || self.template,
333
- val,
334
- encodedVal;
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
-
344
- params = params || {};
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
- }
360
- });
361
-
362
- // set the url
363
- config.url = url.replace(/\/?#$/, '').replace(/\/*$/, '');
364
-
365
- // set params - delegate param encoding to $http
366
- forEach(params, function(value, key){
367
- if (!self.urlParams[key]) {
368
- config.params = config.params || {};
369
- config.params[key] = value;
370
- }
371
- });
372
- }
373
- };
374
-
375
-
376
- function ResourceFactory(url, paramDefaults, actions) {
377
- var route = new Route(url);
378
-
379
- actions = extend({}, DEFAULT_ACTIONS, actions);
380
-
381
- function extractParams(data, actionParams){
382
- var ids = {};
383
- actionParams = extend({}, paramDefaults, actionParams);
384
- forEach(actionParams, function(value, key){
385
- if (isFunction(value)) { value = value(); }
386
- ids[key] = value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value;
387
- });
388
- return ids;
389
- }
390
-
391
- function Resource(value){
392
- copy(value || {}, this);
393
- }
394
-
395
- forEach(actions, function(action, name) {
396
- action.method = angular.uppercase(action.method);
397
- var hasBody = action.method == 'POST' || action.method == 'PUT' || action.method == 'PATCH';
398
- Resource[name] = function(a1, a2, a3, a4) {
399
- var params = {};
400
- var data;
401
- var success = noop;
402
- var error = null;
403
- var promise;
404
-
405
- switch(arguments.length) {
406
- case 4:
407
- error = a4;
408
- success = a3;
409
- //fallthrough
410
- case 3:
411
- case 2:
412
- if (isFunction(a2)) {
413
- if (isFunction(a1)) {
414
- success = a1;
415
- error = a2;
416
- break;
417
- }
418
-
419
- success = a2;
420
- error = a3;
421
- //fallthrough
422
- } else {
423
- params = a1;
424
- data = a2;
425
- success = a3;
426
- break;
427
- }
428
- case 1:
429
- if (isFunction(a1)) success = a1;
430
- else if (hasBody) data = a1;
431
- else params = a1;
432
- break;
433
- case 0: break;
434
- default:
435
- throw "Expected between 0-4 arguments [params, data, success, error], got " +
436
- arguments.length + " arguments.";
437
- }
438
-
439
- var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data));
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;
471
- }
472
- }
473
-
474
- (success||noop)(value, response.headers);
475
-
476
- response.resource = value;
477
- return response;
478
- }, error).then;
479
-
480
- return value;
481
- };
482
-
483
-
484
- Resource.prototype['$' + name] = function(a1, a2, a3) {
485
- var params = extractParams(this),
486
- success = noop,
487
- error;
488
-
489
- switch(arguments.length) {
490
- case 3: params = a1; success = a2; error = a3; break;
491
- case 2:
492
- case 1:
493
- if (isFunction(a1)) {
494
- success = a1;
495
- error = a2;
496
- } else {
497
- params = a1;
498
- success = a2 || noop;
499
- }
500
- case 0: break;
501
- default:
502
- throw "Expected between 1-3 arguments [params, success, error], got " +
503
- arguments.length + " arguments.";
504
- }
505
- var data = hasBody ? this : undefined;
506
- Resource[name].call(this, params, data, success, error);
507
- };
508
- });
509
-
510
- Resource.bind = function(additionalParamDefaults){
511
- return ResourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions);
512
- };
513
-
514
- return Resource;
515
- }
516
-
517
- return ResourceFactory;
518
- }]);
519
-
520
-
521
- })(window, window.angular);