diff --git a/public/javascripts/app/collections/stream.js b/public/javascripts/app/collections/posts.js similarity index 53% rename from public/javascripts/app/collections/stream.js rename to public/javascripts/app/collections/posts.js index b16e137e1..496d5a754 100644 --- a/public/javascripts/app/collections/stream.js +++ b/public/javascripts/app/collections/posts.js @@ -1,13 +1,5 @@ -app.collections.Stream = Backbone.Collection.extend({ - url: function() { - var path = document.location.pathname; - - if(this.models.length) { - path += "?max_time=" + _.last(this.models).createdAt(); - } - - return path; - }, +app.collections.Posts = Backbone.Collection.extend({ + url : "/posts", model: function(attrs, options) { var modelClass = app.models[attrs.post_type] || app.models.Post diff --git a/public/javascripts/app/models/stream.js b/public/javascripts/app/models/stream.js new file mode 100644 index 000000000..8611af3e2 --- /dev/null +++ b/public/javascripts/app/models/stream.js @@ -0,0 +1,36 @@ +app.models.Stream = Backbone.Collection.extend({ + initialize : function(){ + this.posts = new app.collections.Posts(); + }, + + url : function(){ + return _.any(this.posts.models) ? this.timeFilteredPath() : this.basePath() + }, + + fetch: function() { + var self = this + + this.posts + .fetch({ + add : true, + url : self.url() + }) + .done( + function(){ + self.trigger("fetched", self); + } + ) + }, + + basePath : function(){ + return document.location.pathname; + }, + + timeFilteredPath : function(){ + return this.basePath() + "?max_time=" + _.last(this.posts.models).createdAt(); + }, + + add : function(models){ + this.posts.add(models) + } +}) diff --git a/public/javascripts/app/router.js b/public/javascripts/app/router.js index ab7234f59..986e2789b 100644 --- a/public/javascripts/app/router.js +++ b/public/javascripts/app/router.js @@ -13,10 +13,11 @@ app.Router = Backbone.Router.extend({ }, stream : function() { - app.stream = new app.views.Stream().render(); - $("#main_stream").html(app.stream.el); + app.stream = new app.models.Stream() + app.page = new app.views.Stream().render(); + $("#main_stream").html(app.page.el); - var streamFacesView = new app.views.StreamFaces({collection : app.stream.collection}).render(); + var streamFacesView = new app.views.StreamFaces({collection : app.stream.posts}).render(); $('#selected_aspect_contacts .content').html(streamFacesView.el); } }); diff --git a/public/javascripts/app/views/feedback_view.js b/public/javascripts/app/views/feedback_view.js index 84c1ced12..bfa7110ad 100644 --- a/public/javascripts/app/views/feedback_view.js +++ b/public/javascripts/app/views/feedback_view.js @@ -20,7 +20,7 @@ app.views.Feedback = app.views.StreamObject.extend({ reshare.save({}, { url: this.model.createReshareUrl, success : function(){ - app.stream.collection.add(reshare); + app.stream.add(reshare); } }); } diff --git a/public/javascripts/app/views/post_view.js b/public/javascripts/app/views/post_view.js index adb46d6b8..94011ecfc 100644 --- a/public/javascripts/app/views/post_view.js +++ b/public/javascripts/app/views/post_view.js @@ -63,9 +63,9 @@ app.views.Post = app.views.StreamObject.extend({ success : function(){ if(!app.stream) { return } - _.each(app.stream.collection.models, function(model){ + _.each(app.stream.posts.models, function(model){ if(model.get("author").id == personId) { - app.stream.collection.remove(model); + app.stream.posts.remove(model); } }) } diff --git a/public/javascripts/app/views/publisher_view.js b/public/javascripts/app/views/publisher_view.js index 989d2ce9a..68e358ca0 100644 --- a/public/javascripts/app/views/publisher_view.js +++ b/public/javascripts/app/views/publisher_view.js @@ -12,7 +12,7 @@ app.views.Publisher = Backbone.View.extend({ }, initialize : function(){ - this.collection = this.collection || new app.collections.Stream; + this.collection = this.collection || new app.collections.Posts; return this; }, @@ -33,7 +33,7 @@ app.views.Publisher = Backbone.View.extend({ }, { url : "/status_messages", success : function() { - app.stream.collection.add(statusMessage.toJSON()); + app.stream.posts.add(statusMessage.toJSON()); } }); diff --git a/public/javascripts/app/views/stream_faces_view.js b/public/javascripts/app/views/stream_faces_view.js index d0979cd8a..ae5232fc7 100644 --- a/public/javascripts/app/views/stream_faces_view.js +++ b/public/javascripts/app/views/stream_faces_view.js @@ -8,7 +8,7 @@ app.views.StreamFaces = app.views.Base.extend({ initialize : function(){ this.updatePeople() - this.collection.bind("add", this.updatePeople, this) + app.stream.posts.bind("add", this.updatePeople, this) }, presenter : function() { diff --git a/public/javascripts/app/views/stream_view.js b/public/javascripts/app/views/stream_view.js index d8c654697..8f396f392 100644 --- a/public/javascripts/app/views/stream_view.js +++ b/public/javascripts/app/views/stream_view.js @@ -3,25 +3,86 @@ app.views.Stream = Backbone.View.extend({ "click #paginate": "render" }, - initialize: function() { - this.collection = this.collection || new app.collections.Stream; - this.collection.bind("add", this.addPost, this); - + initialize: function(options) { + this.stream = app.stream || new app.models.Stream() + this.collection = this.stream.posts this.publisher = new app.views.Publisher({collection : this.collection}); - // inf scroll - // we're using this._loading to keep track of backbone's collection - // fetching state... is there a better way to do this? - var throttledScroll = _.throttle($.proxy(this.infScroll, this), 200); - $(window).scroll(throttledScroll); + this.stream.bind("fetched", this.collectionFetched, this) + this.collection.bind("add", this.addPost, this); + this.setupInfiniteScroll() + this.setupLightbox() + }, - // lightbox delegation - this.lightbox = Diaspora.BaseWidget.instantiate("Lightbox"); - $(this.el).delegate("a.stream-photo-link", "click", this.lightbox.lightboxImageClicked); + addPost : function(post) { + var postView = new app.views.Post({ model: post }); + + $(this.el)[ + (this.collection.at(0).id == post.id) + ? "prepend" + : "append" + ](postView.render().el); return this; }, + isLoading : function(){ + return this._loading && !this._loading.isResolved(); + }, + + allContentLoaded : false, + + + collectionFetched: function(collection, response) { + this.removeLoader() + if(!collection.parse(response).length || collection.parse(response).length == 0) { + this.allContentLoaded = true; + $(window).unbind('scroll') + return + } + + $(this.el).append($("", { + href: this.stream.url(), + id: "paginate" + }).text('Load more posts')); + }, + + render : function(evt) { + if(evt) { evt.preventDefault(); } + + this.addLoader(); + this._loading = this.stream.fetch(); + + return this; + }, + + addLoader: function(){ + if(this.$("#paginate").length == 0) { + $(this.el).append($("
", { + "id" : "paginate" + })); + } + + this.$("#paginate").html($("", { + src : "/images/static-loader.png", + "class" : "loader" + })); + }, + + removeLoader : function(){ + this.$("#paginate").remove(); + }, + + setupLightbox : function(){ + this.lightbox = Diaspora.BaseWidget.instantiate("Lightbox"); + $(this.el).delegate("a.stream-photo-link", "click", this.lightbox.lightboxImageClicked); + }, + + setupInfiniteScroll : function() { + var throttledScroll = _.throttle($.proxy(this.infScroll, this), 200); + $(window).scroll(throttledScroll); + }, + infScroll : function() { if(this.allContentLoaded || this.isLoading()) { return } @@ -36,64 +97,4 @@ app.views.Stream = Backbone.View.extend({ return this; }, - - isLoading : function(){ - return this._loading && !this._loading.isResolved(); - }, - - allContentLoaded : false, - - addPost : function(post) { - var postView = new app.views.Post({ model: post }); - - $(this.el)[ - (this.collection.at(0).id == post.id) - ? "prepend" - : "append" - ](postView.render().el); - - return this; - }, - - collectionFetched: function(collection, response) { - this.$("#paginate").remove(); - - if(!collection.parse(response).length || collection.parse(response).length == 0) { - this.allContentLoaded = true; - $(window).unbind('scroll') - return - } - - $(this.el).append($("", { - href: this.collection.url(), - id: "paginate" - }).text('Load more posts')); - }, - - render : function(evt) { - if(evt) { evt.preventDefault(); } - - var self = this; - self.addLoader(); - - this._loading = self.collection.fetch({ - add: true, - success: $.proxy(this.collectionFetched, self) - }); - - return this; - }, - - addLoader: function(){ - if(this.$("#paginate").length == 0) { - $(this.el).append($("
", { - "id" : "paginate" - })); - } - - this.$("#paginate").html($("", { - src : "/images/static-loader.png", - "class" : "loader" - })); - } }); diff --git a/spec/javascripts/app/collections/stream_spec.js b/spec/javascripts/app/collections/stream_spec.js deleted file mode 100644 index 145c09c57..000000000 --- a/spec/javascripts/app/collections/stream_spec.js +++ /dev/null @@ -1,19 +0,0 @@ -describe("app.collections.Stream", function() { - describe("url", function() { - var stream = new app.collections.Stream(), - expectedPath = document.location.pathname; - - it("returns the correct path", function() { - expect(stream.url()).toEqual(expectedPath); - }); - - it("returns the json path with max_time if the collection has models", function() { - var post = new app.models.Post(); - spyOn(post, "createdAt").andReturn(1234); - - stream.add(post); - - expect(stream.url()).toEqual(expectedPath + "?max_time=1234"); - }); - }); -}); diff --git a/spec/javascripts/app/models/stream_spec.js b/spec/javascripts/app/models/stream_spec.js new file mode 100644 index 000000000..064b8d8dd --- /dev/null +++ b/spec/javascripts/app/models/stream_spec.js @@ -0,0 +1,39 @@ +describe("app.models.Stream", function() { + beforeEach(function(){ + this.stream = new app.models.Stream(), + this.expectedPath = document.location.pathname; + }) + + describe(".fetch", function() { + var postFetch + beforeEach(function(){ + postFetch = new $.Deferred() + + spyOn(this.stream.posts, "fetch").andCallFake(function(){ + return postFetch + }) + }) + + it("it fetches posts from the window's url, and ads them to tthe collection", function() { + this.stream.fetch() + expect(this.stream.posts.fetch).toHaveBeenCalledWith({ add : true, url : this.expectedPath}); + }); + + it("returns the json path with max_time if the collection has models", function() { + var post = new app.models.Post(); + spyOn(post, "createdAt").andReturn(1234); + this.stream.add(post); + + this.stream.fetch() + expect(this.stream.posts.fetch).toHaveBeenCalledWith({ add : true, url : this.expectedPath + "?max_time=1234"}); + }); + + it("triggers fetched on the stream when it is fetched", function(){ + var fetchedSpy = jasmine.createSpy() + this.stream.bind('fetched', fetchedSpy) + this.stream.fetch() + postFetch.resolve() + expect(fetchedSpy).toHaveBeenCalled() + }) + }); +}); diff --git a/spec/javascripts/app/views/post_view_spec.js b/spec/javascripts/app/views/post_view_spec.js index 790913afd..aedea5128 100644 --- a/spec/javascripts/app/views/post_view_spec.js +++ b/spec/javascripts/app/views/post_view_spec.js @@ -13,7 +13,7 @@ describe("app.views.Post", function(){ var posts = $.parseJSON(spec.readFixture("multi_stream_json"))["posts"]; - this.collection = new app.collections.Stream(posts); + this.collection = new app.collections.Posts(posts); this.statusMessage = this.collection.models[0]; this.reshare = this.collection.models[1]; }) diff --git a/spec/javascripts/app/views/stream_faces_view_spec.js b/spec/javascripts/app/views/stream_faces_view_spec.js index d94a8bc99..8cdf4eb6c 100644 --- a/spec/javascripts/app/views/stream_faces_view_spec.js +++ b/spec/javascripts/app/views/stream_faces_view_spec.js @@ -9,8 +9,11 @@ describe("app.views.StreamFaces", function(){ this.post6 = factory.post({author : rebeccaBlack}) this.post7 = factory.post({author : rebeccaBlack}) - this.stream = new app.collections.Stream([this.post1, this.post2, this.post3, this.post4, this.post5, this.post6, this.post7]); - this.view = new app.views.StreamFaces({collection : this.stream}) + app.stream = new app.models.Stream() + app.stream.add([this.post1, this.post2, this.post3, this.post4, this.post5, this.post6, this.post7]); + this.posts = app.stream.posts + + this.view = new app.views.StreamFaces({collection : this.posts}) }) it("should take them unique", function(){ @@ -19,7 +22,7 @@ describe("app.views.StreamFaces", function(){ }) it("findsPeople when the collection changes", function(){ - this.stream.add(factory.post({author : factory.author({name : "Harriet Tubman"})})) + this.posts.add(factory.post({author : factory.author({name : "Harriet Tubman"})})) expect(this.view.people.length).toBe(6) }) @@ -39,8 +42,8 @@ describe("app.views.StreamFaces", function(){ it("rerenders when people are added, but caps to 15 people", function(){ var posts = _.map(_.range(20), function(){ return factory.post()}) - this.stream.reset(posts) //add 20 posts silently to the collection - this.stream.add(factory.post) //trigger an update + this.posts.reset(posts) //add 20 posts silently to the collection + this.posts.add(factory.post) //trigger an update expect(this.view.$("img").length).toBe(15) }) }) diff --git a/spec/javascripts/app/views/stream_view_spec.js b/spec/javascripts/app/views/stream_view_spec.js index 0ef2cdd8b..38160b033 100644 --- a/spec/javascripts/app/views/stream_view_spec.js +++ b/spec/javascripts/app/views/stream_view_spec.js @@ -4,14 +4,17 @@ describe("app.views.Stream", function(){ this.posts = $.parseJSON(spec.readFixture("multi_stream_json"))["posts"]; - this.collection = new app.collections.Stream(this.posts); + app.stream = new app.models.Stream() + app.stream.add(this.posts); + + this.collection = app.stream.posts this.view = new app.views.Stream({collection : this.collection}); + app.stream.bind("fetched", this.collectionFetched, this) //untested + // do this manually because we've moved loadMore into render?? this.view.render(); - _.each(this.view.collection.models, function(post){ - this.view.addPost(post); - }, this); + _.each(this.view.collection.models, function(post){ this.view.addPost(post); }, this); }) describe("initialize", function(){ @@ -42,7 +45,7 @@ describe("app.views.Stream", function(){ // NOTE: inf scroll happens at 500px beforeEach(function(){ - spyOn(this.view.collection, "fetch") + spyOn(this.view.collection, "fetch").andReturn($.Deferred()) }) context("when the user is at the bottom of the page", function(){