From 4b8806ec1d6d1c25be7222c9aef1198e8035bc68 Mon Sep 17 00:00:00 2001 From: William Dibbern Date: Sun, 15 Sep 2013 18:34:23 -0500 Subject: [PATCH] Infinite Scroll Pagination for content screen Fixes #258 - Modified post collection to have default values for paging. - Added scroll handler to content view to check for more posts and load as appropriate. - Sanitized result from server-side post paging, ensure page # is returned as an integer. - Added a functional test stub. --- core/client/models/post.js | 7 +++ core/client/views/blog.js | 54 +++++++++++++++++++ core/server/models/post.js | 2 +- core/test/functional/admin/04_content_test.js | 14 +++++ 4 files changed, 76 insertions(+), 1 deletion(-) diff --git a/core/client/models/post.js b/core/client/models/post.js index 97de7d5c7e..91fd1d3009 100644 --- a/core/client/models/post.js +++ b/core/client/models/post.js @@ -44,8 +44,15 @@ }); Ghost.Collections.Posts = Backbone.Collection.extend({ + currentPage: 1, + totalPages: 0, + totalPosts: 0, + nextPage: 0, + prevPage: 0, + url: Ghost.settings.apiRoot + '/posts', model: Ghost.Models.Post, + parse: function (resp) { if (_.isArray(resp.posts)) { this.limit = resp.limit; diff --git a/core/client/views/blog.js b/core/client/views/blog.js index dc1c1b7436..158a3d37fe 100644 --- a/core/client/views/blog.js +++ b/core/client/views/blog.js @@ -20,6 +20,8 @@ // ----------------------- ContentList = Ghost.View.extend({ + isLoading: false, + events: { 'click .content-list-content' : 'scrollHandler' }, @@ -27,15 +29,67 @@ initialize: function (options) { this.$('.content-list-content').scrollClass({target: '.content-list', offset: 10}); this.listenTo(this.collection, 'remove', this.showNext); + // Can't use backbone event bind (see: http://stackoverflow.com/questions/13480843/backbone-scroll-event-not-firing) + this.$('.content-list-content').scroll($.proxy(this.checkScroll, this)); }, showNext: function () { + if (this.isLoading) { return; } var id = this.collection.at(0).id; if (id) { Backbone.trigger('blog:activeItem', id); } }, + reportLoadError: function (response) { + var message = 'A problem was encountered while loading more posts'; + + if (response) { + // Get message from response + message += '; ' + Ghost.Views.Utils.getRequestErrorMessage(response); + } else { + message += '.'; + } + + Ghost.notifications.addItem({ + type: 'error', + message: message, + status: 'passive' + }); + }, + + checkScroll: function (event) { + var self = this, + element = event.target, + triggerPoint = 100; + + // If we haven't passed our threshold, exit + if (this.isLoading || (element.scrollTop + element.clientHeight + triggerPoint <= element.scrollHeight)) { + return; + } + + // If we've loaded the max number of pages, exit + if (this.collection.currentPage >= this.collection.totalPages) { + return; + } + + // Load moar posts! + this.isLoading = true; + + this.collection.fetch({ + data: { + status: 'all', + page: (self.collection.currentPage + 1), + orderBy: ['updated_at', 'DESC'] + } + }).then(function onSuccess(response) { + self.render(); + self.isLoading = false; + }, function onError(e) { + self.reportLoadError(e); + }); + }, + render: function () { this.collection.each(function (model) { this.$('ol').append(this.addSubview(new ContentItem({model: model})).render().el); diff --git a/core/server/models/post.js b/core/server/models/post.js index 73f8212f04..f13d2c84c5 100644 --- a/core/server/models/post.js +++ b/core/server/models/post.js @@ -259,7 +259,7 @@ Post = GhostBookshelf.Model.extend({ var totalPosts = resp[0].aggregate, data = { posts: collection.toJSON(), - page: opts.page, + page: parseInt(opts.page, 10), limit: opts.limit, pages: Math.ceil(totalPosts / opts.limit), total: totalPosts diff --git a/core/test/functional/admin/04_content_test.js b/core/test/functional/admin/04_content_test.js index 3ad7f2f646..235fdb552e 100644 --- a/core/test/functional/admin/04_content_test.js +++ b/core/test/functional/admin/04_content_test.js @@ -44,6 +44,20 @@ casper.test.begin("Content screen is correct", 17, function suite(test) { // casper.clickLabel(testPost.title, "h3"); // }); + casper.run(function () { + test.done(); + }); +}); + +casper.test.begin('Infinite scrolling', 1, function suite(test) { + test.filename = 'content_infinite_scrolling_test.png'; + + // Placeholder for infinite scrolling/pagination tests (will need to setup 16+ posts). + + casper.start(url + "ghost/content/", function testTitleAndUrl() { + test.assertTitle("", "Ghost admin has no title"); + }).viewport(1280, 1024); + casper.run(function () { test.done(); });