2011-07-17 08:05:43 +00:00
|
|
|
|
'use strict';
|
|
|
|
|
|
|
2011-02-15 06:12:45 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @ngdoc service
|
|
|
|
|
|
* @name angular.service.$resource
|
|
|
|
|
|
* @requires $xhr.cache
|
|
|
|
|
|
*
|
|
|
|
|
|
* @description
|
2011-02-14 18:17:04 +00:00
|
|
|
|
* A factory which creates a resource object that lets you interact with
|
2011-02-15 06:12:45 +00:00
|
|
|
|
* [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources.
|
|
|
|
|
|
*
|
|
|
|
|
|
* The returned resource object has action methods which provide high-level behaviors without
|
|
|
|
|
|
* the need to interact with the low level {@link angular.service.$xhr $xhr} service or
|
|
|
|
|
|
* raw XMLHttpRequest.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {string} url A parameterized URL template with parameters prefixed by `:` as in
|
|
|
|
|
|
* `/user/:username`.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
|
|
|
|
|
|
* `actions` methods.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Each key value in the parameter object is first bound to url template if present and then any
|
|
|
|
|
|
* excess keys are appended to the url search query after the `?`.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in
|
|
|
|
|
|
* URL `/path/greet?salutation=Hello`.
|
|
|
|
|
|
*
|
|
|
|
|
|
* If the parameter value is prefixed with `@` then the value of that parameter is extracted from
|
|
|
|
|
|
* the data object (useful for non-GET operations).
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {Object.<Object>=} actions Hash with declaration of custom action that should extend the
|
|
|
|
|
|
* default set of resource actions. The declaration should be created in the following format:
|
|
|
|
|
|
*
|
|
|
|
|
|
* {action1: {method:?, params:?, isArray:?, verifyCache:?},
|
|
|
|
|
|
* action2: {method:?, params:?, isArray:?, verifyCache:?},
|
|
|
|
|
|
* ...}
|
|
|
|
|
|
*
|
|
|
|
|
|
* Where:
|
|
|
|
|
|
*
|
|
|
|
|
|
* - `action` – {string} – The name of action. This name becomes the name of the method on your
|
|
|
|
|
|
* resource object.
|
|
|
|
|
|
* - `method` – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, `DELETE`,
|
|
|
|
|
|
* and `JSON` (also known as JSONP).
|
|
|
|
|
|
* - `params` – {object=} – Optional set of pre-bound parameters for this action.
|
|
|
|
|
|
* - isArray – {boolean=} – If true then the returned object for this action is an array, see
|
|
|
|
|
|
* `returns` section.
|
|
|
|
|
|
* - verifyCache – {boolean=} – If true then whenever cache hit occurs, the object is returned and
|
|
|
|
|
|
* an async request will be made to the server and the resources as well as the cache will be
|
|
|
|
|
|
* updated when the response is received.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @returns {Object} A resource "class" object with methods for the default set of resource actions
|
|
|
|
|
|
* optionally extended with custom `actions`. The default set contains these actions:
|
|
|
|
|
|
*
|
|
|
|
|
|
* { 'get': {method:'GET'},
|
|
|
|
|
|
* 'save': {method:'POST'},
|
|
|
|
|
|
* 'query': {method:'GET', isArray:true},
|
|
|
|
|
|
* 'remove': {method:'DELETE'},
|
|
|
|
|
|
* 'delete': {method:'DELETE'} };
|
|
|
|
|
|
*
|
|
|
|
|
|
* Calling these methods invoke an {@link angular.service.$xhr} with the specified http method,
|
|
|
|
|
|
* destination and parameters. When the data is returned from the server then the object is an
|
|
|
|
|
|
* instance of the resource class `save`, `remove` and `delete` actions are available on it as
|
|
|
|
|
|
* methods with the `$` prefix. This allows you to easily perform CRUD operations (create, read,
|
|
|
|
|
|
* update, delete) on server-side data like this:
|
|
|
|
|
|
* <pre>
|
|
|
|
|
|
var User = $resource('/user/:userId', {userId:'@id'});
|
2011-10-07 18:27:49 +00:00
|
|
|
|
var user = User.get({userId:123}, function() {
|
2011-02-15 06:12:45 +00:00
|
|
|
|
user.abc = true;
|
|
|
|
|
|
user.$save();
|
|
|
|
|
|
});
|
|
|
|
|
|
</pre>
|
|
|
|
|
|
*
|
|
|
|
|
|
* It is important to realize that invoking a $resource object method immediately returns an
|
|
|
|
|
|
* empty reference (object or array depending on `isArray`). Once the data is returned from the
|
|
|
|
|
|
* server the existing reference is populated with the actual data. This is a useful trick since
|
|
|
|
|
|
* usually the resource is assigned to a model which is then rendered by the view. Having an empty
|
|
|
|
|
|
* object results in no rendering, once the data arrives from the server then the object is
|
|
|
|
|
|
* populated with the data and the view automatically re-renders itself showing the new data. This
|
|
|
|
|
|
* means that in most case one never has to write a callback function for the action methods.
|
|
|
|
|
|
*
|
|
|
|
|
|
* The action methods on the class object or instance object can be invoked with the following
|
|
|
|
|
|
* parameters:
|
|
|
|
|
|
*
|
2011-07-22 19:56:45 +00:00
|
|
|
|
* - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])`
|
|
|
|
|
|
* - non-GET "class" actions: `Resource.action(postData, [parameters], [success], [error])`
|
|
|
|
|
|
* - non-GET instance actions: `instance.$action([parameters], [success], [error])`
|
2011-02-15 06:12:45 +00:00
|
|
|
|
*
|
|
|
|
|
|
*
|
|
|
|
|
|
* @example
|
|
|
|
|
|
*
|
|
|
|
|
|
* # Credit card resource
|
|
|
|
|
|
*
|
|
|
|
|
|
* <pre>
|
|
|
|
|
|
// Define CreditCard class
|
|
|
|
|
|
var CreditCard = $resource('/user/:userId/card/:cardId',
|
|
|
|
|
|
{userId:123, cardId:'@id'}, {
|
|
|
|
|
|
charge: {method:'POST', params:{charge:true}}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// We can retrieve a collection from the server
|
|
|
|
|
|
var cards = CreditCard.query();
|
|
|
|
|
|
// GET: /user/123/card
|
|
|
|
|
|
// server returns: [ {id:456, number:'1234', name:'Smith'} ];
|
|
|
|
|
|
|
|
|
|
|
|
var card = cards[0];
|
|
|
|
|
|
// each item is an instance of CreditCard
|
|
|
|
|
|
expect(card instanceof CreditCard).toEqual(true);
|
|
|
|
|
|
card.name = "J. Smith";
|
|
|
|
|
|
// non GET methods are mapped onto the instances
|
|
|
|
|
|
card.$save();
|
|
|
|
|
|
// POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
|
|
|
|
|
|
// server returns: {id:456, number:'1234', name: 'J. Smith'};
|
|
|
|
|
|
|
|
|
|
|
|
// our custom method is mapped as well.
|
|
|
|
|
|
card.$charge({amount:9.99});
|
|
|
|
|
|
// POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
|
|
|
|
|
|
// server returns: {id:456, number:'1234', name: 'J. Smith'};
|
|
|
|
|
|
|
|
|
|
|
|
// we can create an instance as well
|
|
|
|
|
|
var newCard = new CreditCard({number:'0123'});
|
|
|
|
|
|
newCard.name = "Mike Smith";
|
|
|
|
|
|
newCard.$save();
|
|
|
|
|
|
// POST: /user/123/card {number:'0123', name:'Mike Smith'}
|
|
|
|
|
|
// server returns: {id:789, number:'01234', name: 'Mike Smith'};
|
|
|
|
|
|
expect(newCard.id).toEqual(789);
|
|
|
|
|
|
* </pre>
|
|
|
|
|
|
*
|
|
|
|
|
|
* The object returned from this function execution is a resource "class" which has "static" method
|
|
|
|
|
|
* for each action in the definition.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Calling these methods invoke `$xhr` on the `url` template with the given `method` and `params`.
|
|
|
|
|
|
* When the data is returned from the server then the object is an instance of the resource type and
|
|
|
|
|
|
* all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD
|
|
|
|
|
|
* operations (create, read, update, delete) on server-side data.
|
|
|
|
|
|
|
|
|
|
|
|
<pre>
|
|
|
|
|
|
var User = $resource('/user/:userId', {userId:'@id'});
|
2011-10-07 18:27:49 +00:00
|
|
|
|
var user = User.get({userId:123}, function() {
|
2011-02-15 06:12:45 +00:00
|
|
|
|
user.abc = true;
|
|
|
|
|
|
user.$save();
|
|
|
|
|
|
});
|
|
|
|
|
|
</pre>
|
|
|
|
|
|
*
|
2011-07-22 19:56:45 +00:00
|
|
|
|
* It's worth noting that the success callback for `get`, `query` and other method gets passed
|
|
|
|
|
|
* in the response that came from the server, so one could rewrite the above example as:
|
2011-02-15 06:12:45 +00:00
|
|
|
|
*
|
|
|
|
|
|
<pre>
|
|
|
|
|
|
var User = $resource('/user/:userId', {userId:'@id'});
|
|
|
|
|
|
User.get({userId:123}, function(u){
|
|
|
|
|
|
u.abc = true;
|
|
|
|
|
|
u.$save();
|
|
|
|
|
|
});
|
|
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
|
|
|
|
* # Buzz client
|
|
|
|
|
|
|
|
|
|
|
|
Let's look at what a buzz client created with the `$resource` service looks like:
|
|
|
|
|
|
<doc:example>
|
2011-08-11 03:53:56 +00:00
|
|
|
|
<doc:source jsfiddle="false">
|
2011-02-15 06:12:45 +00:00
|
|
|
|
<script>
|
|
|
|
|
|
function BuzzController($resource) {
|
2011-09-08 20:56:29 +00:00
|
|
|
|
this.userId = 'googlebuzz';
|
2011-02-15 06:12:45 +00:00
|
|
|
|
this.Activity = $resource(
|
|
|
|
|
|
'https://www.googleapis.com/buzz/v1/activities/:userId/:visibility/:activityId/:comments',
|
|
|
|
|
|
{alt:'json', callback:'JSON_CALLBACK'},
|
|
|
|
|
|
{get:{method:'JSON', params:{visibility:'@self'}}, replies: {method:'JSON', params:{visibility:'@self', comments:'@comments'}}}
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
BuzzController.prototype = {
|
|
|
|
|
|
fetch: function() {
|
|
|
|
|
|
this.activities = this.Activity.get({userId:this.userId});
|
|
|
|
|
|
},
|
|
|
|
|
|
expandReplies: function(activity) {
|
|
|
|
|
|
activity.replies = this.Activity.replies({userId:this.userId, activityId:activity.id});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
BuzzController.$inject = ['$resource'];
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<div ng:controller="BuzzController">
|
2011-09-08 20:56:29 +00:00
|
|
|
|
<input ng:model="userId"/>
|
2011-02-15 06:12:45 +00:00
|
|
|
|
<button ng:click="fetch()">fetch</button>
|
|
|
|
|
|
<hr/>
|
|
|
|
|
|
<div ng:repeat="item in activities.data.items">
|
|
|
|
|
|
<h1 style="font-size: 15px;">
|
|
|
|
|
|
<img src="{{item.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
|
|
|
|
|
|
<a href="{{item.actor.profileUrl}}">{{item.actor.name}}</a>
|
|
|
|
|
|
<a href ng:click="expandReplies(item)" style="float: right;">Expand replies: {{item.links.replies[0].count}}</a>
|
|
|
|
|
|
</h1>
|
|
|
|
|
|
{{item.object.content | html}}
|
|
|
|
|
|
<div ng:repeat="reply in item.replies.data.items" style="margin-left: 20px;">
|
|
|
|
|
|
<img src="{{reply.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
|
|
|
|
|
|
<a href="{{reply.actor.profileUrl}}">{{reply.actor.name}}</a>: {{reply.content | html}}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</doc:source>
|
|
|
|
|
|
<doc:scenario>
|
|
|
|
|
|
</doc:scenario>
|
|
|
|
|
|
</doc:example>
|
|
|
|
|
|
*/
|
2011-11-02 23:32:46 +00:00
|
|
|
|
function $ResourceProvider() {
|
|
|
|
|
|
this.$get = ['$xhr.cache', function($xhr){
|
|
|
|
|
|
var resource = new ResourceFactory($xhr);
|
|
|
|
|
|
return bind(resource, resource.route);
|
|
|
|
|
|
}];
|
|
|
|
|
|
}
|