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";'); +      }); +    }); + +  }); + +  });  | 
