angular-gem 1.1.3 → 1.1.4

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