diff options
| -rw-r--r-- | scenario/cross-site-post/People.json | 4 | ||||
| -rw-r--r-- | scenario/cross-site-post/index.html | 10 | ||||
| -rw-r--r-- | src/Angular.js | 8 | ||||
| -rw-r--r-- | src/Browser.js | 4 | ||||
| -rw-r--r-- | src/Scope.js | 4 | ||||
| -rw-r--r-- | src/services.js | 87 | ||||
| -rw-r--r-- | test/ResourceSpec.js | 9 | ||||
| -rw-r--r-- | test/servicesSpec.js | 82 |
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";'); + }); + }); + + }); + + }); |
