aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--scenario/cross-site-post/People.json4
-rw-r--r--scenario/cross-site-post/index.html10
-rw-r--r--src/Angular.js8
-rw-r--r--src/Browser.js4
-rw-r--r--src/Scope.js4
-rw-r--r--src/services.js87
-rw-r--r--test/ResourceSpec.js9
-rw-r--r--test/servicesSpec.js82
8 files changed, 197 insertions, 11 deletions
diff --git a/scenario/cross-site-post/People.json b/scenario/cross-site-post/People.json
new file mode 100644
index 00000000..de51fd83
--- /dev/null
+++ b/scenario/cross-site-post/People.json
@@ -0,0 +1,4 @@
+[
+ { name: 'Misko', favorite: ['water melon', 'persimmon', 'passion fruit'] },
+ { name: 'Lenka', favorite: ['strawberry'] }
+]
diff --git a/scenario/cross-site-post/index.html b/scenario/cross-site-post/index.html
new file mode 100644
index 00000000..3ff6af85
--- /dev/null
+++ b/scenario/cross-site-post/index.html
@@ -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>
diff --git a/src/Angular.js b/src/Angular.js
index 8eef7275..3af21ced 100644
--- a/src/Angular.js
+++ b/src/Angular.js
@@ -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)) {
diff --git a/src/Browser.js b/src/Browser.js
index 11b079f0..d2e8608d 100644
--- a/src/Browser.js
+++ b/src/Browser.js
@@ -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('');
diff --git a/src/Scope.js b/src/Scope.js
index 54e75dbd..1b93418f 100644
--- a/src/Scope.js
+++ b/src/Scope.js
@@ -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);
}
});
diff --git a/src/services.js b/src/services.js
index b854c8d6..36c89ccb 100644
--- a/src/services.js
+++ b/src/services.js
@@ -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']});
diff --git a/test/ResourceSpec.js b/test/ResourceSpec.js
index f2a0ff41..f0bb6770 100644
--- a/test/ResourceSpec.js
+++ b/test/ResourceSpec.js
@@ -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');
+ });
+
});
diff --git a/test/servicesSpec.js b/test/servicesSpec.js
index a7391f34..f15d6ad7 100644
--- a/test/servicesSpec.js
+++ b/test/servicesSpec.js
@@ -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";');
+ });
+ });
+
+ });
+
+
});