added $xhr service with bulk and cache, hooked up $resource

This commit is contained in:
Misko Hevery 2010-04-29 17:28:33 -07:00
parent 913729ee01
commit c7913a4b7a
8 changed files with 197 additions and 11 deletions

View file

@ -0,0 +1,4 @@
[
{ name: 'Misko', favorite: ['water melon', 'persimmon', 'passion fruit'] },
{ name: 'Lenka', favorite: ['strawberry'] }
]

View file

@ -0,0 +1,10 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<script type="text/javascript" src="../../src/angular-bootstrap.js#autobind"></script>
</head>
<body ng:init="$window.$scope = this; People = $resource('People.json')">
<button ng-click="people = People.query()">Load People</button>
<pre>people = {{people}}</pre>
</body>
</html>

View file

@ -231,12 +231,12 @@ function isLeafNode (node) {
function copy(source, destination){
if (!destination) {
if (!source) {
return source;
} else if (isArray(source)) {
if (isArray(source)) {
return copy(source, []);
} else {
} else if (isObject(source)) {
return copy(source, {});
} else {
return source;
}
} else {
if (isArray(source)) {

View file

@ -52,12 +52,12 @@ Browser.prototype = {
head.append(link);
},
xhr: function(method, url, callback){
xhr: function(method, url, post, callback){
var xhr = new this.XHR();
xhr.open(method, url, true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
callback(xhr.status, xhr.responseText);
callback(xhr.status || 200, xhr.responseText);
}
};
xhr.send('');

View file

@ -173,7 +173,7 @@ function createScope(parent, services, existing) {
}
function inject(name){
var service = getter(servicesCache, name), factory, args = [];
var service = getter(servicesCache, name, true), factory, args = [];
if (isUndefined(service)) {
factory = services[name];
if (!isFunction(factory))
@ -189,7 +189,7 @@ function createScope(parent, services, existing) {
foreach(services, function(_, name){
var service = inject(name);
if (service) {
instance[name] = service;
setter(instance, name, service);
}
});

View file

@ -189,7 +189,88 @@ angularService('$route', function(location, params){
return $route;
}, {inject: ['$location']});
angularService('$resource', function(browser){
var resource = new ResourceFactory(bind(browser, browser.xhr));
angularService('$xhr', function($browser){
var self = this;
return function(method, url, post, callback){
if (post && isObject(post)) {
post = toJson(post);
}
$browser.xhr(method, url, post, function(code, response){
try {
if (isString(response) && /^\s*[\[\{]/.exec(response) && /[\}\]]\s*$/.exec(response)) {
response = fromJson(response);
}
callback(code, response);
} finally {
self.$eval();
}
});
};
}, {inject:['$browser']});
angularService('$xhr.bulk', function($xhr){
var requests = [],
callbacks = [],
scope = this;
function bulkXHR(method, url, post, callback) {
requests.push({method: method, url: url, data:post});
callbacks.push(callback);
}
bulkXHR.url = "/bulk";
bulkXHR.flush = function(callback){
var currentRequests = requests,
currentCallbacks = callbacks;
requests = [];
callbacks = [];
$xhr('POST', bulkXHR.url, {requests:currentRequests}, function(code, response){
foreach(response, function(response, i){
try {
(currentCallbacks[i] || noop)(response.status, response.response);
} catch(e) {
self.$log.error(e);
}
});
(callback || noop)();
});
scope.$eval();
};
return bulkXHR;
}, {inject:['$xhr']});
angularService('$xhr.cache', function($xhr){
var inflight = {};
function cache(method, url, post, callback){
if (method == 'GET') {
var data;
if (data = cache.data[url]) {
callback(200, copy(data.value));
} else if (data = inflight[url]) {
data.callbacks.push(callback);
} else {
inflight[url] = {callbacks: [callback]};
cache.delegate(method, url, post, function(status, response){
if (status == 200)
cache.data[url] = { value: response };
foreach(inflight[url].callbacks, function(callback){
try {
(callback||noop)(status, copy(response));
} catch(e) {
self.$log.error(e);
}
});
delete inflight[url];
});
}
} else {
cache.delegate(method, url, post, callback);
}
}
cache.data = {};
cache.delegate = $xhr;
return cache;
}, {inject:['$xhr']});
angularService('$resource', function($xhr){
var resource = new ResourceFactory($xhr);
return bind(resource, resource.route);
}, {inject: ['$browser']});
}, {inject: ['$xhr.cache']});

View file

@ -120,4 +120,13 @@ describe("resource", function() {
nakedExpect(visa).toEqual({id:123});
});
it('should excersize full stack', function(){
var scope = angular.compile('<div></div>');
var Person = scope.$resource('/Person/:id');
scope.$browser.xhr.expectGET('/Person/123').respond('\n{\nname:\n"misko"\n}\n');
var person = Person.get({id:123});
scope.$browser.xhr.flush();
expect(person.name).toEqual('misko');
});
});

View file

@ -159,6 +159,88 @@ describe("service", function(){
});
});
describe('$xhr', function(){
var log, xhr;
function callback(code, response) {
expect(code).toEqual(200);
log = log + toJson(response) + ';';
};
beforeEach(function(){
log = '';
xhr = scope.$browser.xhr;
});
it('should forward the request to $browser and decode JSON', function(){
xhr.expectGET('/reqGET').respond('first');
xhr.expectGET('/reqGETjson').respond('["second"]');
xhr.expectPOST('/reqPOST', {post:'data'}).respond('third');
scope.$xhr('GET', '/reqGET', null, callback);
scope.$xhr('GET', '/reqGETjson', null, callback);
scope.$xhr('POST', '/reqPOST', {post:'data'}, callback);
xhr.flush();
expect(log).toEqual('"third";["second"];"first";');
});
describe('bulk', function(){
it('should collect requests', function(){
scope.$xhr.bulk.url = "/";
scope.$xhr.bulk('GET', '/req1', null, callback);
scope.$xhr.bulk('POST', '/req2', {post:'data'}, callback);
xhr.expectPOST('/', {
requests:[{method:'GET', url:'/req1', data: null},
{method:'POST', url:'/req2', data:{post:'data'} }]
}).respond([
{status:200, response:'first'},
{status:200, response:'second'}
]);
scope.$xhr.bulk.flush(function(){ log += 'DONE';});
xhr.flush();
expect(log).toEqual('"first";"second";DONE');
});
});
describe('cache', function(){
var cache;
beforeEach(function(){ cache = scope.$xhr.cache; });
it('should cache requests', function(){
xhr.expectGET('/url').respond('first');
cache('GET', '/url', null, callback);
xhr.flush();
xhr.expectGET('/url').respond('ERROR');
cache('GET', '/url', null, callback);
xhr.flush();
expect(log).toEqual('"first";"first";');
});
it('should serve requests from cache', function(){
cache.data.url = {value:'123'};
cache('GET', 'url', null, callback);
expect(log).toEqual('"123";');
});
it('should keep track of in flight requests and request only once', function(){
cache.delegate = scope.$xhr.bulk;
xhr.expectPOST('/bulk', {
requests:[{method:'GET', url:'/url', data: null}]
}).respond([
{status:200, response:'123'},
]);
cache('GET', '/url', null, callback);
cache('GET', '/url', null, callback);
cache.delegate.flush();
xhr.flush();
expect(log).toEqual('"123";"123";');
});
});
});
});