Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit c7913a4

Browse files
committed
added $xhr service with bulk and cache, hooked up $resource
1 parent 913729e commit c7913a4

File tree

8 files changed

+197
-11
lines changed

8 files changed

+197
-11
lines changed

scenario/cross-site-post/People.json

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[
2+
{ name: 'Misko', favorite: ['water melon', 'persimmon', 'passion fruit'] },
3+
{ name: 'Lenka', favorite: ['strawberry'] }
4+
]

scenario/cross-site-post/index.html

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

src/Angular.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -231,12 +231,12 @@ function isLeafNode (node) {
231231

232232
function copy(source, destination){
233233
if (!destination) {
234-
if (!source) {
235-
return source;
236-
} else if (isArray(source)) {
234+
if (isArray(source)) {
237235
return copy(source, []);
238-
} else {
236+
} else if (isObject(source)) {
239237
return copy(source, {});
238+
} else {
239+
return source;
240240
}
241241
} else {
242242
if (isArray(source)) {

src/Browser.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,12 @@ Browser.prototype = {
5252
head.append(link);
5353
},
5454

55-
xhr: function(method, url, callback){
55+
xhr: function(method, url, post, callback){
5656
var xhr = new this.XHR();
5757
xhr.open(method, url, true);
5858
xhr.onreadystatechange = function() {
5959
if (xhr.readyState == 4) {
60-
callback(xhr.status, xhr.responseText);
60+
callback(xhr.status || 200, xhr.responseText);
6161
}
6262
};
6363
xhr.send('');

src/Scope.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ function createScope(parent, services, existing) {
173173
}
174174

175175
function inject(name){
176-
var service = getter(servicesCache, name), factory, args = [];
176+
var service = getter(servicesCache, name, true), factory, args = [];
177177
if (isUndefined(service)) {
178178
factory = services[name];
179179
if (!isFunction(factory))
@@ -189,7 +189,7 @@ function createScope(parent, services, existing) {
189189
foreach(services, function(_, name){
190190
var service = inject(name);
191191
if (service) {
192-
instance[name] = service;
192+
setter(instance, name, service);
193193
}
194194
});
195195

src/services.js

+84-3
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,88 @@ angularService('$route', function(location, params){
189189
return $route;
190190
}, {inject: ['$location']});
191191

192-
angularService('$resource', function(browser){
193-
var resource = new ResourceFactory(bind(browser, browser.xhr));
192+
angularService('$xhr', function($browser){
193+
var self = this;
194+
return function(method, url, post, callback){
195+
if (post && isObject(post)) {
196+
post = toJson(post);
197+
}
198+
$browser.xhr(method, url, post, function(code, response){
199+
try {
200+
if (isString(response) && /^\s*[\[\{]/.exec(response) && /[\}\]]\s*$/.exec(response)) {
201+
response = fromJson(response);
202+
}
203+
callback(code, response);
204+
} finally {
205+
self.$eval();
206+
}
207+
});
208+
};
209+
}, {inject:['$browser']});
210+
211+
angularService('$xhr.bulk', function($xhr){
212+
var requests = [],
213+
callbacks = [],
214+
scope = this;
215+
function bulkXHR(method, url, post, callback) {
216+
requests.push({method: method, url: url, data:post});
217+
callbacks.push(callback);
218+
}
219+
bulkXHR.url = "/bulk";
220+
bulkXHR.flush = function(callback){
221+
var currentRequests = requests,
222+
currentCallbacks = callbacks;
223+
requests = [];
224+
callbacks = [];
225+
$xhr('POST', bulkXHR.url, {requests:currentRequests}, function(code, response){
226+
foreach(response, function(response, i){
227+
try {
228+
(currentCallbacks[i] || noop)(response.status, response.response);
229+
} catch(e) {
230+
self.$log.error(e);
231+
}
232+
});
233+
(callback || noop)();
234+
});
235+
scope.$eval();
236+
};
237+
return bulkXHR;
238+
}, {inject:['$xhr']});
239+
240+
angularService('$xhr.cache', function($xhr){
241+
var inflight = {};
242+
function cache(method, url, post, callback){
243+
if (method == 'GET') {
244+
var data;
245+
if (data = cache.data[url]) {
246+
callback(200, copy(data.value));
247+
} else if (data = inflight[url]) {
248+
data.callbacks.push(callback);
249+
} else {
250+
inflight[url] = {callbacks: [callback]};
251+
cache.delegate(method, url, post, function(status, response){
252+
if (status == 200)
253+
cache.data[url] = { value: response };
254+
foreach(inflight[url].callbacks, function(callback){
255+
try {
256+
(callback||noop)(status, copy(response));
257+
} catch(e) {
258+
self.$log.error(e);
259+
}
260+
});
261+
delete inflight[url];
262+
});
263+
}
264+
} else {
265+
cache.delegate(method, url, post, callback);
266+
}
267+
}
268+
cache.data = {};
269+
cache.delegate = $xhr;
270+
return cache;
271+
}, {inject:['$xhr']});
272+
273+
angularService('$resource', function($xhr){
274+
var resource = new ResourceFactory($xhr);
194275
return bind(resource, resource.route);
195-
}, {inject: ['$browser']});
276+
}, {inject: ['$xhr.cache']});

test/ResourceSpec.js

+9
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,13 @@ describe("resource", function() {
120120
nakedExpect(visa).toEqual({id:123});
121121
});
122122

123+
it('should excersize full stack', function(){
124+
var scope = angular.compile('<div></div>');
125+
var Person = scope.$resource('/Person/:id');
126+
scope.$browser.xhr.expectGET('/Person/123').respond('\n{\nname:\n"misko"\n}\n');
127+
var person = Person.get({id:123});
128+
scope.$browser.xhr.flush();
129+
expect(person.name).toEqual('misko');
130+
});
131+
123132
});

test/servicesSpec.js

+82
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,88 @@ describe("service", function(){
159159
});
160160
});
161161

162+
describe('$xhr', function(){
163+
var log, xhr;
164+
function callback(code, response) {
165+
expect(code).toEqual(200);
166+
log = log + toJson(response) + ';';
167+
};
168+
169+
beforeEach(function(){
170+
log = '';
171+
xhr = scope.$browser.xhr;
172+
});
173+
174+
it('should forward the request to $browser and decode JSON', function(){
175+
xhr.expectGET('/reqGET').respond('first');
176+
xhr.expectGET('/reqGETjson').respond('["second"]');
177+
xhr.expectPOST('/reqPOST', {post:'data'}).respond('third');
178+
179+
scope.$xhr('GET', '/reqGET', null, callback);
180+
scope.$xhr('GET', '/reqGETjson', null, callback);
181+
scope.$xhr('POST', '/reqPOST', {post:'data'}, callback);
182+
183+
xhr.flush();
184+
185+
expect(log).toEqual('"third";["second"];"first";');
186+
});
187+
188+
describe('bulk', function(){
189+
it('should collect requests', function(){
190+
scope.$xhr.bulk.url = "/";
191+
scope.$xhr.bulk('GET', '/req1', null, callback);
192+
scope.$xhr.bulk('POST', '/req2', {post:'data'}, callback);
193+
194+
xhr.expectPOST('/', {
195+
requests:[{method:'GET', url:'/req1', data: null},
196+
{method:'POST', url:'/req2', data:{post:'data'} }]
197+
}).respond([
198+
{status:200, response:'first'},
199+
{status:200, response:'second'}
200+
]);
201+
scope.$xhr.bulk.flush(function(){ log += 'DONE';});
202+
xhr.flush();
203+
expect(log).toEqual('"first";"second";DONE');
204+
});
205+
});
206+
207+
describe('cache', function(){
208+
var cache;
209+
beforeEach(function(){ cache = scope.$xhr.cache; });
210+
it('should cache requests', function(){
211+
xhr.expectGET('/url').respond('first');
212+
cache('GET', '/url', null, callback);
213+
xhr.flush();
214+
xhr.expectGET('/url').respond('ERROR');
215+
cache('GET', '/url', null, callback);
216+
xhr.flush();
217+
expect(log).toEqual('"first";"first";');
218+
});
219+
220+
it('should serve requests from cache', function(){
221+
cache.data.url = {value:'123'};
222+
cache('GET', 'url', null, callback);
223+
expect(log).toEqual('"123";');
224+
});
225+
226+
it('should keep track of in flight requests and request only once', function(){
227+
cache.delegate = scope.$xhr.bulk;
228+
xhr.expectPOST('/bulk', {
229+
requests:[{method:'GET', url:'/url', data: null}]
230+
}).respond([
231+
{status:200, response:'123'},
232+
]);
233+
cache('GET', '/url', null, callback);
234+
cache('GET', '/url', null, callback);
235+
cache.delegate.flush();
236+
xhr.flush();
237+
expect(log).toEqual('"123";"123";');
238+
});
239+
});
240+
241+
});
242+
243+
162244
});
163245

164246

0 commit comments

Comments
 (0)