diff --git a/app/views/aspects/_aspect_stream.haml b/app/views/aspects/_aspect_stream.haml index 564b1d9612b01620e7479f1444d9b314ad89672a..0ddc45b9488826f2c9a46e7e67d5d82a50d390d7 100644 --- a/app/views/aspects/_aspect_stream.haml +++ b/app/views/aspects/_aspect_stream.haml @@ -10,7 +10,9 @@ #gs-shim{:title => popover_with_close_html("3. #{t('.stay_updated')}"), 'data-content' => t('.stay_updated_explanation')} -#main_stream.stream{:data => {:guids => stream.aspect_ids.join(',')}} +#main_stream.stream + +#paginate - if current_user.contacts.size < 2 = render 'aspects/no_contacts_message' diff --git a/public/javascripts/app/models/stream.js b/public/javascripts/app/models/stream.js index 8611af3e28d538a4b4e2bc855ba39c45ef34e6a6..bee78eebb6882c1423c9a352050f42d3b57542e1 100644 --- a/public/javascripts/app/models/stream.js +++ b/public/javascripts/app/models/stream.js @@ -7,19 +7,33 @@ app.models.Stream = Backbone.Collection.extend({ return _.any(this.posts.models) ? this.timeFilteredPath() : this.basePath() }, + _fetching : false, + fetch: function() { var self = this + // we're fetching the collection... there is probably a better way to do this + self._fetching = true; + this.posts .fetch({ add : true, url : self.url() }) .done( - function(){ + function(resp){ + // we're done fetching... there is probably a better way to handle this + self._fetching = false; + self.trigger("fetched", self); + + // all loaded? + if(resp.posts && (resp.posts.author || resp.posts.length == 0)) { + self.trigger("allPostsLoaded", self); + } } ) + return this; }, basePath : function(){ diff --git a/public/javascripts/app/router.js b/public/javascripts/app/router.js index 986e2789b3013805c2094b01db36bbc6bfa2f441..effd9dadcb3f3f05e81cdc7538731d6e0d15d523 100644 --- a/public/javascripts/app/router.js +++ b/public/javascripts/app/router.js @@ -14,10 +14,12 @@ app.Router = Backbone.Router.extend({ stream : function() { app.stream = new app.models.Stream() - app.page = new app.views.Stream().render(); - $("#main_stream").html(app.page.el); + app.page = new app.views.Stream({model : app.stream}).render(); + app.publisher = app.publisher || new app.views.Publisher({collection : app.stream.posts}); var streamFacesView = new app.views.StreamFaces({collection : app.stream.posts}).render(); + + $("#main_stream").html(app.page.el); $('#selected_aspect_contacts .content').html(streamFacesView.el); } }); diff --git a/public/javascripts/app/views/commment_stream_view.js b/public/javascripts/app/views/commment_stream_view.js index 5348ed55d0f55fbf6d918da257c52d5c8bad8fd9..f47a1a00c167d97764bd452b870eb0a9c48534df 100644 --- a/public/javascripts/app/views/commment_stream_view.js +++ b/public/javascripts/app/views/commment_stream_view.js @@ -12,6 +12,9 @@ app.views.CommentStream = app.views.Base.extend({ initialize: function(options) { this.model.comments.bind('add', this.appendComment, this); + + // add autoexpanders to new comment textarea + this.$('textarea').autoResize(); }, postRenderTemplate : function() { diff --git a/public/javascripts/app/views/publisher_view.js b/public/javascripts/app/views/publisher_view.js index 68e358ca048a6175828764e8fb18d74fd9316b87..b33f4ea2e5ce1aa98e9bc6db573bdf5129851715 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.Posts; + this.collection = this.collection //takes a Posts collection return this; }, diff --git a/public/javascripts/app/views/stream_view.js b/public/javascripts/app/views/stream_view.js index 8f396f39215464cc32c14248d3eeeb118004a6c1..7dbd08c14898622d64e0c5b00b866a34ca8a9fbf 100644 --- a/public/javascripts/app/views/stream_view.js +++ b/public/javascripts/app/views/stream_view.js @@ -4,16 +4,20 @@ app.views.Stream = Backbone.View.extend({ }, 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}); + this.stream = this.model + this.collection = this.model.posts - this.stream.bind("fetched", this.collectionFetched, this) - this.collection.bind("add", this.addPost, this); + this.setupEvents() this.setupInfiniteScroll() this.setupLightbox() }, + setupEvents : function(){ + this.stream.bind("fetched", this.removeLoader, this) + this.stream.bind("allPostsLoaded", this.unbindInfScroll, this) + this.collection.bind("add", this.addPost, this); + }, + addPost : function(post) { var postView = new app.views.Post({ model: post }); @@ -26,51 +30,30 @@ app.views.Stream = Backbone.View.extend({ 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($("<a>", { - href: this.stream.url(), - id: "paginate" - }).text('Load more posts')); + unbindInfScroll : function() { + $("window").unbind("scroll"); }, render : function(evt) { if(evt) { evt.preventDefault(); } - this.addLoader(); - this._loading = this.stream.fetch(); + // fetch more posts from the stream model + if(this.stream.fetch()) { + this.appendLoader() + }; return this; }, - addLoader: function(){ - if(this.$("#paginate").length == 0) { - $(this.el).append($("<div>", { - "id" : "paginate" - })); - } - - this.$("#paginate").html($("<img>", { + appendLoader: function(){ + $("#paginate").html($("<img>", { src : "/images/static-loader.png", "class" : "loader" })); }, - removeLoader : function(){ - this.$("#paginate").remove(); + removeLoader: function() { + $("#paginate").empty(); }, setupLightbox : function(){ @@ -80,13 +63,11 @@ app.views.Stream = Backbone.View.extend({ setupInfiniteScroll : function() { var throttledScroll = _.throttle($.proxy(this.infScroll, this), 200); - $(window).scroll(throttledScroll); + $("window").scroll(throttledScroll); }, infScroll : function() { - if(this.allContentLoaded || this.isLoading()) { return } - - var $window = $(window); + var $window = $("window"); var distFromTop = $window.height() + $window.scrollTop(); var distFromBottom = $(document).height() - distFromTop; var bufferPx = 500; diff --git a/spec/javascripts/app/models/stream_spec.js b/spec/javascripts/app/models/stream_spec.js index 064b8d8ddbda502e783e477aa86356931c2b354f..c7a18f62146cb66423d6263df75adf718bd93787 100644 --- a/spec/javascripts/app/models/stream_spec.js +++ b/spec/javascripts/app/models/stream_spec.js @@ -9,7 +9,7 @@ describe("app.models.Stream", function() { beforeEach(function(){ postFetch = new $.Deferred() - spyOn(this.stream.posts, "fetch").andCallFake(function(){ + spyOn(this.stream.posts, "fetch").andCallFake(function(){ return postFetch }) }) @@ -32,7 +32,23 @@ describe("app.models.Stream", function() { var fetchedSpy = jasmine.createSpy() this.stream.bind('fetched', fetchedSpy) this.stream.fetch() - postFetch.resolve() + postFetch.resolve({posts : [1,2,3]}) + expect(fetchedSpy).toHaveBeenCalled() + }) + + it("triggers allPostsLoaded on the stream when zero posts are returned", function(){ + var fetchedSpy = jasmine.createSpy() + this.stream.bind('allPostsLoaded', fetchedSpy) + this.stream.fetch() + postFetch.resolve({posts : []}) + expect(fetchedSpy).toHaveBeenCalled() + }) + + it("triggers allPostsLoaded on the stream when a Post is returned", function(){ + var fetchedSpy = jasmine.createSpy() + this.stream.bind('allPostsLoaded', fetchedSpy) + this.stream.fetch() + postFetch.resolve({posts : factory.post().attributes}) expect(fetchedSpy).toHaveBeenCalled() }) }); diff --git a/spec/javascripts/app/views/stream_view_spec.js b/spec/javascripts/app/views/stream_view_spec.js index 38160b033cb3a921d4884fc8a625cf20130e0dc5..5f3b1d3714a80dfef31d3896040ce42f4269775b 100644 --- a/spec/javascripts/app/views/stream_view_spec.js +++ b/spec/javascripts/app/views/stream_view_spec.js @@ -4,11 +4,10 @@ describe("app.views.Stream", function(){ this.posts = $.parseJSON(spec.readFixture("multi_stream_json"))["posts"]; - app.stream = new app.models.Stream() - app.stream.add(this.posts); + this.stream = new app.models.Stream() + this.stream.add(this.posts); - this.collection = app.stream.posts - this.view = new app.views.Stream({collection : this.collection}); + this.view = new app.views.Stream({model : this.stream}); app.stream.bind("fetched", this.collectionFetched, this) //untested @@ -20,16 +19,15 @@ describe("app.views.Stream", function(){ describe("initialize", function(){ it("binds an infinite scroll listener", function(){ spyOn($.fn, "scroll"); - - new app.views.Stream(); + new app.views.Stream({model : this.stream}); expect($.fn.scroll).toHaveBeenCalled() }) }) describe("#render", function(){ beforeEach(function(){ - this.statusMessage = this.collection.models[0]; - this.reshare = this.collection.models[1]; + this.statusMessage = this.stream.posts.models[0]; + this.reshare = this.stream.posts.models[1]; this.statusElement = $(this.view.$("#" + this.statusMessage.get("guid"))); this.reshareElement = $(this.view.$("#" + this.reshare.get("guid"))); }) @@ -44,77 +42,31 @@ describe("app.views.Stream", function(){ describe("infScroll", function(){ // NOTE: inf scroll happens at 500px - beforeEach(function(){ - spyOn(this.view.collection, "fetch").andReturn($.Deferred()) - }) - - context("when the user is at the bottom of the page", function(){ - beforeEach(function(){ - spyOn($.fn, "height").andReturn(0) - spyOn($.fn, "scrollTop").andReturn(100) - }) - - it("calls fetch", function(){ - spyOn(this.view, "isLoading").andReturn(false) - - this.view.infScroll(); - expect(this.view.collection.fetch).toHaveBeenCalled(); - }) - - it("does not call fetch if the collection is loading", function(){ - spyOn(this.view, "isLoading").andReturn(true) - - this.view.infScroll(); - expect(this.view.collection.fetch).not.toHaveBeenCalled(); - }) - - it("does not call fetch if all content has been fetched", function(){ - spyOn(this.view, "isLoading").andReturn(false) - this.view.allContentLoaded = true; - - this.view.infScroll(); - expect(this.view.collection.fetch).not.toHaveBeenCalled(); - }) - }) - - it("does not fetch new content when the user is not at the bottom of the page", function(){ - spyOn(this.view, "isLoading").andReturn(false) - - spyOn($.fn, "height").andReturn(0); - spyOn($.fn, "scrollTop").andReturn(-500); + it("calls render when the user is at the bottom of the page", function(){ + spyOn($.fn, "height").andReturn(0) + spyOn($.fn, "scrollTop").andReturn(100) + spyOn(this.view, "render") this.view.infScroll(); - expect(this.view.collection.fetch).not.toHaveBeenCalled(); + expect(this.view.render).toHaveBeenCalled(); }) }) - describe("collectionFetched", function(){ - context("unbinding scroll", function(){ - beforeEach(function(){ - spyOn($.fn, "unbind") - }) - - it("unbinds scroll if there are no more posts left to load", function(){ - this.view.collectionFetched(this.collection, {posts : []}) - expect($.fn.unbind).toHaveBeenCalled() - }) - - it("does not fetch new content when the user is fetching one post", function(){ - this.view.collectionFetched(this.collection, {posts : {}}) - expect($.fn.unbind).toHaveBeenCalled() - }) - }) - - it("sets this.allContentLoaded if there are no more posts left to load", function(){ - expect(this.view.allContentLoaded).toBe(false) - this.view.collectionFetched(this.collection, {posts : []}) - expect(this.view.allContentLoaded).toBe(true) + describe("removeLoader", function() { + it("emptys the pagination div when the stream is fetched", function(){ + $("#jasmine_content").append($('<div id="paginate">OMG</div>')) + expect($("#paginate").text()).toBe("OMG") + this.view.stream.trigger("fetched") + expect($("#paginate")).toBeEmpty() }) + }) - it("does not set this.allContentLoaded if there was a non-empty response from the server", function(){ - expect(this.view.allContentLoaded).toBe(false) - this.view.collectionFetched(this.collection, {posts : this.posts}) - expect(this.view.allContentLoaded).toBe(false) + describe("unbindInfScroll", function(){ + it("unbinds scroll", function() { + spyOn($.fn, "unbind") + this.view.unbindInfScroll() + expect($.fn.unbind.mostRecentCall.object.selector).toBe("window") + expect($.fn.unbind).toHaveBeenCalledWith("scroll") }) }) })