}` –
92 | * transform function or an array of such functions. The transform function takes the http
93 | * response body and headers and returns its transformed (typically deserialized) version.
94 | * - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
95 | * GET request, otherwise if a cache instance built with
96 | * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
97 | * caching.
98 | * - **`timeout`** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} that
99 | * should abort the request when resolved.
100 | * - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the
101 | * XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5
102 | * requests with credentials} for more information.
103 | * - **`responseType`** - `{string}` - see {@link
104 | * https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}.
105 | * - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods -
106 | * `response` and `responseError`. Both `response` and `responseError` interceptors get called
107 | * with `http response` object. See {@link ng.$http $http interceptors}.
108 | *
109 | * @returns {Object} A resource "class" object with methods for the default set of resource actions
110 | * optionally extended with custom `actions`. The default set contains these actions:
111 | *
112 | * { 'get': {method:'GET'},
113 | * 'save': {method:'POST'},
114 | * 'query': {method:'GET', isArray:true},
115 | * 'remove': {method:'DELETE'},
116 | * 'delete': {method:'DELETE'} };
117 | *
118 | * Calling these methods invoke an {@link ng.$http} with the specified http method,
119 | * destination and parameters. When the data is returned from the server then the object is an
120 | * instance of the resource class. The actions `save`, `remove` and `delete` are available on it
121 | * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create,
122 | * read, update, delete) on server-side data like this:
123 | *
124 | var User = $resource('/user/:userId', {userId:'@id'});
125 | var user = User.get({userId:123}, function() {
126 | user.abc = true;
127 | user.$save();
128 | });
129 |
130 | *
131 | * It is important to realize that invoking a $resource object method immediately returns an
132 | * empty reference (object or array depending on `isArray`). Once the data is returned from the
133 | * server the existing reference is populated with the actual data. This is a useful trick since
134 | * usually the resource is assigned to a model which is then rendered by the view. Having an empty
135 | * object results in no rendering, once the data arrives from the server then the object is
136 | * populated with the data and the view automatically re-renders itself showing the new data. This
137 | * means that in most case one never has to write a callback function for the action methods.
138 | *
139 | * The action methods on the class object or instance object can be invoked with the following
140 | * parameters:
141 | *
142 | * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])`
143 | * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])`
144 | * - non-GET instance actions: `instance.$action([parameters], [success], [error])`
145 | *
146 | * Success callback is called with (value, responseHeaders) arguments. Error callback is called
147 | * with (httpResponse) argument.
148 | *
149 | * Class actions return empty instance (with additional properties below).
150 | * Instance actions return promise of the action.
151 | *
152 | * The Resource instances and collection have these additional properties:
153 | *
154 | * - `$promise`: the {@link ng.$q promise} of the original server interaction that created this
155 | * instance or collection.
156 | *
157 | * On success, the promise is resolved with the same resource instance or collection object,
158 | * updated with data from server. This makes it easy to use in
159 | * {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view
160 | * rendering until the resource(s) are loaded.
161 | *
162 | * On failure, the promise is resolved with the {@link ng.$http http response} object, without
163 | * the `resource` property.
164 | *
165 | * - `$resolved`: `true` after first server interaction is completed (either with success or
166 | * rejection), `false` before that. Knowing if the Resource has been resolved is useful in
167 | * data-binding.
168 | *
169 | * @example
170 | *
171 | * # Credit card resource
172 | *
173 | *
174 | // Define CreditCard class
175 | var CreditCard = $resource('/user/:userId/card/:cardId',
176 | {userId:123, cardId:'@id'}, {
177 | charge: {method:'POST', params:{charge:true}}
178 | });
179 |
180 | // We can retrieve a collection from the server
181 | var cards = CreditCard.query(function() {
182 | // GET: /user/123/card
183 | // server returns: [ {id:456, number:'1234', name:'Smith'} ];
184 |
185 | var card = cards[0];
186 | // each item is an instance of CreditCard
187 | expect(card instanceof CreditCard).toEqual(true);
188 | card.name = "J. Smith";
189 | // non GET methods are mapped onto the instances
190 | card.$save();
191 | // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
192 | // server returns: {id:456, number:'1234', name: 'J. Smith'};
193 |
194 | // our custom method is mapped as well.
195 | card.$charge({amount:9.99});
196 | // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
197 | });
198 |
199 | // we can create an instance as well
200 | var newCard = new CreditCard({number:'0123'});
201 | newCard.name = "Mike Smith";
202 | newCard.$save();
203 | // POST: /user/123/card {number:'0123', name:'Mike Smith'}
204 | // server returns: {id:789, number:'01234', name: 'Mike Smith'};
205 | expect(newCard.id).toEqual(789);
206 | *
207 | *
208 | * The object returned from this function execution is a resource "class" which has "static" method
209 | * for each action in the definition.
210 | *
211 | * Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and
212 | * `headers`.
213 | * When the data is returned from the server then the object is an instance of the resource type and
214 | * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD
215 | * operations (create, read, update, delete) on server-side data.
216 |
217 |
218 | var User = $resource('/user/:userId', {userId:'@id'});
219 | var user = User.get({userId:123}, function() {
220 | user.abc = true;
221 | user.$save();
222 | });
223 |
224 | *
225 | * It's worth noting that the success callback for `get`, `query` and other methods gets passed
226 | * in the response that came from the server as well as $http header getter function, so one
227 | * could rewrite the above example and get access to http headers as:
228 | *
229 |
230 | var User = $resource('/user/:userId', {userId:'@id'});
231 | User.get({userId:123}, function(u, getResponseHeaders){
232 | u.abc = true;
233 | u.$save(function(u, putResponseHeaders) {
234 | //u => saved user object
235 | //putResponseHeaders => $http header getter
236 | });
237 | });
238 |
239 |
240 | * # Buzz client
241 |
242 | Let's look at what a buzz client created with the `$resource` service looks like:
243 |
244 |
245 |
268 |
269 |
270 |
271 |
fetch
272 |
273 |
274 |
280 | {{item.object.content | html}}
281 |
285 |
286 |
287 |
288 |
289 |
290 |
291 | */
292 | angular.module('ngResource', ['ng']).
293 | factory('$resource', ['$http', '$parse', '$q', function($http, $parse, $q) {
294 | var DEFAULT_ACTIONS = {
295 | 'get': {method:'GET'},
296 | 'save': {method:'POST'},
297 | 'query': {method:'GET', isArray:true},
298 | 'remove': {method:'DELETE'},
299 | 'delete': {method:'DELETE'}
300 | };
301 | var noop = angular.noop,
302 | forEach = angular.forEach,
303 | extend = angular.extend,
304 | copy = angular.copy,
305 | isFunction = angular.isFunction,
306 | getter = function(obj, path) {
307 | return $parse(path)(obj);
308 | };
309 |
310 | /**
311 | * We need our custom method because encodeURIComponent is too aggressive and doesn't follow
312 | * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
313 | * segments:
314 | * segment = *pchar
315 | * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
316 | * pct-encoded = "%" HEXDIG HEXDIG
317 | * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
318 | * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
319 | * / "*" / "+" / "," / ";" / "="
320 | */
321 | function encodeUriSegment(val) {
322 | return encodeUriQuery(val, true).
323 | replace(/%26/gi, '&').
324 | replace(/%3D/gi, '=').
325 | replace(/%2B/gi, '+');
326 | }
327 |
328 |
329 | /**
330 | * This method is intended for encoding *key* or *value* parts of query component. We need a
331 | * custom method because encodeURIComponent is too aggressive and encodes stuff that doesn't
332 | * have to be encoded per http://tools.ietf.org/html/rfc3986:
333 | * query = *( pchar / "/" / "?" )
334 | * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
335 | * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
336 | * pct-encoded = "%" HEXDIG HEXDIG
337 | * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
338 | * / "*" / "+" / "," / ";" / "="
339 | */
340 | function encodeUriQuery(val, pctEncodeSpaces) {
341 | return encodeURIComponent(val).
342 | replace(/%40/gi, '@').
343 | replace(/%3A/gi, ':').
344 | replace(/%24/g, '$').
345 | replace(/%2C/gi, ',').
346 | replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
347 | }
348 |
349 | function Route(template, defaults) {
350 | this.template = template;
351 | this.defaults = defaults || {};
352 | this.urlParams = {};
353 | }
354 |
355 | Route.prototype = {
356 | setUrlParams: function(config, params, actionUrl) {
357 | var self = this,
358 | url = actionUrl || self.template,
359 | val,
360 | encodedVal;
361 |
362 | var urlParams = self.urlParams = {};
363 | forEach(url.split(/\W/), function(param){
364 | if (param === 'hasOwnProperty') {
365 | throw $resourceMinErr('badname', "hasOwnProperty is not a valid parameter name.");
366 | }
367 | if (!(new RegExp("^\\d+$").test(param)) && param &&
368 | (new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) {
369 | urlParams[param] = true;
370 | }
371 | });
372 | url = url.replace(/\\:/g, ':');
373 |
374 | params = params || {};
375 | forEach(self.urlParams, function(_, urlParam){
376 | val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam];
377 | if (angular.isDefined(val) && val !== null) {
378 | encodedVal = encodeUriSegment(val);
379 | url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), encodedVal + "$1");
380 | } else {
381 | url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match,
382 | leadingSlashes, tail) {
383 | if (tail.charAt(0) == '/') {
384 | return tail;
385 | } else {
386 | return leadingSlashes + tail;
387 | }
388 | });
389 | }
390 | });
391 |
392 | // strip trailing slashes and set the url
393 | url = url.replace(/\/+$/, '');
394 | // then replace collapse `/.` if found in the last URL path segment before the query
395 | // E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x`
396 | url = url.replace(/\/\.(?=\w+($|\?))/, '.');
397 | // replace escaped `/\.` with `/.`
398 | config.url = url.replace(/\/\\\./, '/.');
399 |
400 |
401 | // set params - delegate param encoding to $http
402 | forEach(params, function(value, key){
403 | if (!self.urlParams[key]) {
404 | config.params = config.params || {};
405 | config.params[key] = value;
406 | }
407 | });
408 | }
409 | };
410 |
411 |
412 | function resourceFactory(url, paramDefaults, actions) {
413 | var route = new Route(url);
414 |
415 | actions = extend({}, DEFAULT_ACTIONS, actions);
416 |
417 | function extractParams(data, actionParams){
418 | var ids = {};
419 | actionParams = extend({}, paramDefaults, actionParams);
420 | forEach(actionParams, function(value, key){
421 | if (isFunction(value)) { value = value(); }
422 | ids[key] = value && value.charAt && value.charAt(0) == '@' ?
423 | getter(data, value.substr(1)) : value;
424 | });
425 | return ids;
426 | }
427 |
428 | function defaultResponseInterceptor(response) {
429 | return response.resource;
430 | }
431 |
432 | function Resource(value){
433 | copy(value || {}, this);
434 | }
435 |
436 | forEach(actions, function(action, name) {
437 | var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method);
438 |
439 | Resource[name] = function(a1, a2, a3, a4) {
440 | var params = {}, data, success, error;
441 |
442 | /* jshint -W086 */ /* (purposefully fall through case statements) */
443 | switch(arguments.length) {
444 | case 4:
445 | error = a4;
446 | success = a3;
447 | //fallthrough
448 | case 3:
449 | case 2:
450 | if (isFunction(a2)) {
451 | if (isFunction(a1)) {
452 | success = a1;
453 | error = a2;
454 | break;
455 | }
456 |
457 | success = a2;
458 | error = a3;
459 | //fallthrough
460 | } else {
461 | params = a1;
462 | data = a2;
463 | success = a3;
464 | break;
465 | }
466 | case 1:
467 | if (isFunction(a1)) success = a1;
468 | else if (hasBody) data = a1;
469 | else params = a1;
470 | break;
471 | case 0: break;
472 | default:
473 | throw $resourceMinErr('badargs',
474 | "Expected up to 4 arguments [params, data, success, error], got {0} arguments",
475 | arguments.length);
476 | }
477 | /* jshint +W086 */ /* (purposefully fall through case statements) */
478 |
479 | var isInstanceCall = data instanceof Resource;
480 | var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data));
481 | var httpConfig = {};
482 | var responseInterceptor = action.interceptor && action.interceptor.response ||
483 | defaultResponseInterceptor;
484 | var responseErrorInterceptor = action.interceptor && action.interceptor.responseError ||
485 | undefined;
486 |
487 | forEach(action, function(value, key) {
488 | if (key != 'params' && key != 'isArray' && key != 'interceptor') {
489 | httpConfig[key] = copy(value);
490 | }
491 | });
492 |
493 | if (hasBody) httpConfig.data = data;
494 | route.setUrlParams(httpConfig,
495 | extend({}, extractParams(data, action.params || {}), params),
496 | action.url);
497 |
498 | var promise = $http(httpConfig).then(function(response) {
499 | var data = response.data,
500 | promise = value.$promise;
501 |
502 | if (data) {
503 | // Need to convert action.isArray to boolean in case it is undefined
504 | // jshint -W018
505 | if ( angular.isArray(data) !== (!!action.isArray) ) {
506 | throw $resourceMinErr('badcfg', 'Error in resource configuration. Expected ' +
507 | 'response to contain an {0} but got an {1}',
508 | action.isArray?'array':'object', angular.isArray(data)?'array':'object');
509 | }
510 | // jshint +W018
511 | if (action.isArray) {
512 | value.length = 0;
513 | forEach(data, function(item) {
514 | value.push(new Resource(item));
515 | });
516 | } else {
517 | copy(data, value);
518 | value.$promise = promise;
519 | }
520 | }
521 |
522 | value.$resolved = true;
523 |
524 | response.resource = value;
525 |
526 | return response;
527 | }, function(response) {
528 | value.$resolved = true;
529 |
530 | (error||noop)(response);
531 |
532 | return $q.reject(response);
533 | });
534 |
535 | promise = promise.then(
536 | function(response) {
537 | var value = responseInterceptor(response);
538 | (success||noop)(value, response.headers);
539 | return value;
540 | },
541 | responseErrorInterceptor);
542 |
543 | if (!isInstanceCall) {
544 | // we are creating instance / collection
545 | // - set the initial promise
546 | // - return the instance / collection
547 | value.$promise = promise;
548 | value.$resolved = false;
549 |
550 | return value;
551 | }
552 |
553 | // instance call
554 | return promise;
555 | };
556 |
557 |
558 | Resource.prototype['$' + name] = function(params, success, error) {
559 | if (isFunction(params)) {
560 | error = success; success = params; params = {};
561 | }
562 | var result = Resource[name](params, this, success, error);
563 | return result.$promise || result;
564 | };
565 | });
566 |
567 | Resource.bind = function(additionalParamDefaults){
568 | return resourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions);
569 | };
570 |
571 | return Resource;
572 | }
573 |
574 | return resourceFactory;
575 | }]);
576 |
577 |
578 | })(window, window.angular);
579 |
--------------------------------------------------------------------------------
/angular-resource.min.js:
--------------------------------------------------------------------------------
1 | /*
2 | AngularJS v1.2.0
3 | (c) 2010-2012 Google, Inc. http://angularjs.org
4 | License: MIT
5 | */
6 | (function(H,h,C){'use strict';var x=h.$$minErr("$resource");h.module("ngResource",["ng"]).factory("$resource",["$http","$parse","$q",function(D,y,E){function n(h,k){this.template=h;this.defaults=k||{};this.urlParams={}}function t(e,k,f){function q(b,c){var d={};c=u({},k,c);r(c,function(a,c){s(a)&&(a=a());var m;a&&a.charAt&&"@"==a.charAt(0)?(m=a.substr(1),m=y(m)(b)):m=a;d[c]=m});return d}function d(b){return b.resource}function g(b){z(b||{},this)}var F=new n(e);f=u({},G,f);r(f,function(b,c){var A=
7 | /^(POST|PUT|PATCH)$/i.test(b.method);g[c]=function(a,c,m,k){var p={},e,f,v;switch(arguments.length){case 4:v=k,f=m;case 3:case 2:if(s(c)){if(s(a)){f=a;v=c;break}f=c;v=m}else{p=a;e=c;f=m;break}case 1:s(a)?f=a:A?e=a:p=a;break;case 0:break;default:throw x("badargs",arguments.length);}var n=e instanceof g,l=n?e:b.isArray?[]:new g(e),w={},t=b.interceptor&&b.interceptor.response||d,y=b.interceptor&&b.interceptor.responseError||C;r(b,function(a,c){"params"!=c&&("isArray"!=c&&"interceptor"!=c)&&(w[c]=z(a))});
8 | A&&(w.data=e);F.setUrlParams(w,u({},q(e,b.params||{}),p),b.url);p=D(w).then(function(c){var a=c.data,d=l.$promise;if(a){if(h.isArray(a)!==!!b.isArray)throw x("badcfg",b.isArray?"array":"object",h.isArray(a)?"array":"object");b.isArray?(l.length=0,r(a,function(a){l.push(new g(a))})):(z(a,l),l.$promise=d)}l.$resolved=!0;c.resource=l;return c},function(a){l.$resolved=!0;(v||B)(a);return E.reject(a)});p=p.then(function(a){var c=t(a);(f||B)(c,a.headers);return c},y);return n?p:(l.$promise=p,l.$resolved=
9 | !1,l)};g.prototype["$"+c]=function(a,b,d){s(a)&&(d=b,b=a,a={});a=g[c](a,this,b,d);return a.$promise||a}});g.bind=function(b){return t(e,u({},k,b),f)};return g}var G={get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"},"delete":{method:"DELETE"}},B=h.noop,r=h.forEach,u=h.extend,z=h.copy,s=h.isFunction;n.prototype={setUrlParams:function(e,k,f){var q=this,d=f||q.template,g,n,b=q.urlParams={};r(d.split(/\W/),function(c){if("hasOwnProperty"===c)throw x("badname");
10 | !/^\d+$/.test(c)&&(c&&RegExp("(^|[^\\\\]):"+c+"(\\W|$)").test(d))&&(b[c]=!0)});d=d.replace(/\\:/g,":");k=k||{};r(q.urlParams,function(c,b){g=k.hasOwnProperty(b)?k[b]:q.defaults[b];h.isDefined(g)&&null!==g?(n=encodeURIComponent(g).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"%20").replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+"),d=d.replace(RegExp(":"+b+"(\\W|$)","g"),n+"$1")):d=d.replace(RegExp("(/?):"+b+"(\\W|$)","g"),function(a,
11 | c,b){return"/"==b.charAt(0)?b:c+b})});d=d.replace(/\/+$/,"");d=d.replace(/\/\.(?=\w+($|\?))/,".");e.url=d.replace(/\/\\\./,"/.");r(k,function(c,b){q.urlParams[b]||(e.params=e.params||{},e.params[b]=c)})}};return t}])})(window,window.angular);
12 | //# sourceMappingURL=angular-resource.min.js.map
13 |
--------------------------------------------------------------------------------
/component.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-resource",
3 | "repo": "components/angular-resource",
4 | "version": "1.2.0",
5 | "main": "angular-resource.js",
6 | "scripts": [
7 | "angular-resource.js"
8 | ],
9 | "files": [
10 | "angular-resource.min.js"
11 | ],
12 | "dependencies": {
13 | "components/angular.js": "1.2.0"
14 | },
15 | "license": "MIT"
16 | }
17 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "components/angular-resource",
3 | "description": "Resource module for AngularJS",
4 | "type": "component",
5 | "homepage": "http://angularjs.org",
6 | "license": "MIT",
7 | "require": {
8 | "components/angular.js": "1.2.0"
9 | },
10 | "extra": {
11 | "component": {
12 | "name": "angular-resource",
13 | "scripts": [
14 | "angular-resource.js"
15 | ],
16 | "files": [
17 | "angular-resource.min.js"
18 | ]
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------