From 79032a17fb1f458aae723098ddc1a96c28daad05 Mon Sep 17 00:00:00 2001 From: Bernhard Posselt Date: Fri, 30 May 2014 00:06:49 +0200 Subject: add autopaging --- js/app/Config.js | 12 +- js/build/app.js | 106 ++++++++++-- js/controller/ContentController.js | 54 ++++-- js/service/ItemResource.js | 36 +++- js/tests/unit/controller/ContentControllerSpec.js | 191 ++++++++++++++++++++- .../unit/controller/NavigationControllerSpec.js | 1 + js/tests/unit/service/ItemResourceSpec.js | 57 ++++++ 7 files changed, 416 insertions(+), 41 deletions(-) (limited to 'js') diff --git a/js/app/Config.js b/js/app/Config.js index 5aebc3653..772547178 100644 --- a/js/app/Config.js +++ b/js/app/Config.js @@ -79,22 +79,26 @@ app.config(function ($routeProvider, $provide, $httpProvider) { .when('/items', { controller: 'ContentController as Content', templateUrl: 'content.html', - resolve: getResolve(feedType.SUBSCRIPTIONS) + resolve: getResolve(feedType.SUBSCRIPTIONS), + type: feedType.SUBSCRIPTIONS }) .when('/items/starred', { controller: 'ContentController as Content', templateUrl: 'content.html', - resolve: getResolve(feedType.STARRED) + resolve: getResolve(feedType.STARRED), + type: feedType.STARRED }) .when('/items/feeds/:id', { controller: 'ContentController as Content', templateUrl: 'content.html', - resolve: getResolve(feedType.FEED) + resolve: getResolve(feedType.FEED), + type: feedType.FEED }) .when('/items/folders/:id', { controller: 'ContentController as Content', templateUrl: 'content.html', - resolve: getResolve(feedType.FOLDER) + resolve: getResolve(feedType.FOLDER), + type: feedType.FOLDER }) .otherwise({ redirectTo: '/items' diff --git a/js/build/app.js b/js/build/app.js index eb16bafb3..b7d44526e 100644 --- a/js/build/app.js +++ b/js/build/app.js @@ -68,19 +68,23 @@ var $__build_47_app__ = function () { $routeProvider.when('/items', { controller: 'ContentController as Content', templateUrl: 'content.html', - resolve: getResolve(feedType.SUBSCRIPTIONS) + resolve: getResolve(feedType.SUBSCRIPTIONS), + type: feedType.SUBSCRIPTIONS }).when('/items/starred', { controller: 'ContentController as Content', templateUrl: 'content.html', - resolve: getResolve(feedType.STARRED) + resolve: getResolve(feedType.STARRED), + type: feedType.STARRED }).when('/items/feeds/:id', { controller: 'ContentController as Content', templateUrl: 'content.html', - resolve: getResolve(feedType.FEED) + resolve: getResolve(feedType.FEED), + type: feedType.FEED }).when('/items/folders/:id', { controller: 'ContentController as Content', templateUrl: 'content.html', - resolve: getResolve(feedType.FOLDER) + resolve: getResolve(feedType.FOLDER), + type: feedType.FOLDER }).otherwise({ redirectTo: '/items' }); } ]); @@ -181,7 +185,9 @@ var $__build_47_app__ = function () { 'ItemResource', 'SettingsResource', 'data', - function (Publisher, FeedResource, ItemResource, SettingsResource, data) { + '$route', + '$routeParams', + function (Publisher, FeedResource, ItemResource, SettingsResource, data, $route, $routeParams) { 'use strict'; var $__0 = this; this.isAutoPagingEnabled = true; @@ -194,9 +200,11 @@ var $__build_47_app__ = function () { ItemResource.toggleStar(itemId); }; this.markRead = function (itemId) { - ItemResource.markItemRead(itemId); var item = ItemResource.get(itemId); - FeedResource.markItemOfFeedRead(item.feedId); + if (!item.keepUnread) { + ItemResource.markItemRead(itemId); + FeedResource.markItemOfFeedRead(item.feedId); + } }; this.getFeed = function (feedId) { return FeedResource.getById(feedId); @@ -219,21 +227,50 @@ var $__build_47_app__ = function () { this.isCompactView = function () { return SettingsResource.get('compact'); }; - this.getRelativeDate = function (timestamp) { - console.log(timestamp); - }; - this.autoPage = function () { - console.log('hi'); - }; - this.scrollRead = function (itemIds) { - console.log(itemIds); - }; this.autoPagingEnabled = function () { return $__0.isAutoPagingEnabled; }; this.markReadEnabled = function () { return !SettingsResource.get('preventReadOnScroll'); }; + this.scrollRead = function (itemIds) { + var ids = []; + for (var $__3 = itemIds[$traceurRuntime.toProperty(Symbol.iterator)](), $__4; !($__4 = $__3.next()).done;) { + try { + throw undefined; + } catch (itemId) { + itemId = $__4.value; + { + try { + throw undefined; + } catch (item) { + item = ItemResource.get(itemId); + if (!item.keepUnread) { + ids.push(itemId); + FeedResource.markItemOfFeedRead(item.feedId); + } + } + } + } + } + ItemResource.markItemsRead(ids); + }; + this.autoPage = function () { + $__0.isAutoPagingEnabled = false; + var type = $route.current.$$route.type; + var id = $routeParams.id; + ItemResource.autoPage(type, id).success(function (data) { + Publisher.publishAll(data); + if (data.items.length > 0) { + $__0.isAutoPagingEnabled = true; + } + }).error(function () { + $__0.isAutoPagingEnabled = true; + }); + }; + this.getRelativeDate = function (timestamp) { + console.log(timestamp); + }; } ]); app.controller('NavigationController', [ @@ -473,14 +510,16 @@ var $__build_47_app__ = function () { 'Resource', '$http', 'BASE_URL', - function (Resource, $http, BASE_URL) { + 'ITEM_BATCH_SIZE', + function (Resource, $http, BASE_URL, ITEM_BATCH_SIZE) { 'use strict'; - var ItemResource = function ItemResource($http, BASE_URL) { + var ItemResource = function ItemResource($http, BASE_URL, ITEM_BATCH_SIZE) { $traceurRuntime.superCall(this, $ItemResource.prototype, 'constructor', [ $http, BASE_URL ]); this.starredCount = 0; + this.batchSize = ITEM_BATCH_SIZE; }; var $ItemResource = ItemResource; $traceurRuntime.createClass(ItemResource, { @@ -537,6 +576,23 @@ var $__build_47_app__ = function () { data: { isRead: isRead } }); }, + markItemsRead: function (itemIds) { + for (var $__3 = itemIds[$traceurRuntime.toProperty(Symbol.iterator)](), $__4; !($__4 = $__3.next()).done;) { + try { + throw undefined; + } catch (itemId) { + itemId = $__4.value; + { + this.get(itemId).unread = false; + } + } + } + return this.http({ + url: this.BASE_URL + '/items/read/multiple', + method: 'POST', + data: { itemIds: itemIds } + }); + }, markFeedRead: function (feedId) { var read = arguments[1] !== void 0 ? arguments[1] : true; for (var $__3 = this.values.filter(function (i) { @@ -568,9 +624,21 @@ var $__build_47_app__ = function () { }, clear: function () { $traceurRuntime.superCall(this, $ItemResource.prototype, 'clear', []); + }, + autoPage: function (type, id) { + return this.http({ + url: this.BASE_URL + '/items', + method: 'GET', + params: { + type: type, + id: id, + offset: this.size(), + limit: this.batchSize + } + }); } }, {}, Resource); - return new ItemResource($http, BASE_URL); + return new ItemResource($http, BASE_URL, ITEM_BATCH_SIZE); } ]); app.service('Loading', function () { diff --git a/js/controller/ContentController.js b/js/controller/ContentController.js index 804c898e5..5a75f68ab 100644 --- a/js/controller/ContentController.js +++ b/js/controller/ContentController.js @@ -8,7 +8,8 @@ * @copyright Bernhard Posselt 2014 */ app.controller('ContentController', -function (Publisher, FeedResource, ItemResource, SettingsResource, data) { +function (Publisher, FeedResource, ItemResource, SettingsResource, data, + $route, $routeParams) { 'use strict'; this.isAutoPagingEnabled = true; @@ -27,10 +28,12 @@ function (Publisher, FeedResource, ItemResource, SettingsResource, data) { }; this.markRead = (itemId) => { - ItemResource.markItemRead(itemId); - let item = ItemResource.get(itemId); - FeedResource.markItemOfFeedRead(item.feedId); + + if (!item.keepUnread) { + ItemResource.markItemRead(itemId); + FeedResource.markItemOfFeedRead(item.feedId); + } }; this.getFeed = (feedId) => { @@ -59,25 +62,48 @@ function (Publisher, FeedResource, ItemResource, SettingsResource, data) { return SettingsResource.get('compact'); }; - // TBD - this.getRelativeDate = (timestamp) => { - console.log(timestamp); + this.autoPagingEnabled = () => { + return this.isAutoPagingEnabled; }; - this.autoPage = () => { - console.log('hi'); + this.markReadEnabled = () => { + return !SettingsResource.get('preventReadOnScroll'); }; this.scrollRead = (itemIds) => { - console.log(itemIds); + let ids = []; + + for (let itemId of itemIds) { + let item = ItemResource.get(itemId); + if (!item.keepUnread) { + ids.push(itemId); + FeedResource.markItemOfFeedRead(item.feedId); + } + } + + ItemResource.markItemsRead(ids); }; - this.autoPagingEnabled = () => { - return this.isAutoPagingEnabled; + this.autoPage = () => { + this.isAutoPagingEnabled = false; + + let type = $route.current.$$route.type; + let id = $routeParams.id; + + ItemResource.autoPage(type, id).success((data) => { + Publisher.publishAll(data); + + if (data.items.length > 0) { + this.isAutoPagingEnabled = true; + } + }).error(() => { + this.isAutoPagingEnabled = true; + }); }; - this.markReadEnabled = () => { - return !SettingsResource.get('preventReadOnScroll'); + // TBD + this.getRelativeDate = (timestamp) => { + console.log(timestamp); }; }); \ No newline at end of file diff --git a/js/service/ItemResource.js b/js/service/ItemResource.js index 625f58b5e..f0bc0d485 100644 --- a/js/service/ItemResource.js +++ b/js/service/ItemResource.js @@ -7,15 +7,16 @@ * @author Bernhard Posselt * @copyright Bernhard Posselt 2014 */ -app.factory('ItemResource', (Resource, $http, BASE_URL) => { +app.factory('ItemResource', (Resource, $http, BASE_URL, ITEM_BATCH_SIZE) => { 'use strict'; class ItemResource extends Resource { - constructor ($http, BASE_URL) { + constructor ($http, BASE_URL, ITEM_BATCH_SIZE) { super($http, BASE_URL); this.starredCount = 0; + this.batchSize = ITEM_BATCH_SIZE; } @@ -88,6 +89,21 @@ app.factory('ItemResource', (Resource, $http, BASE_URL) => { } + markItemsRead (itemIds) { + for (let itemId of itemIds) { + this.get(itemId).unread = false; + } + + return this.http({ + url: `${this.BASE_URL}/items/read/multiple`, + method: 'POST', + data: { + itemIds: itemIds + } + }); + } + + markFeedRead (feedId, read=true) { for (let item of this.values.filter(i => i.feedId === feedId)) { item.unread = !read; @@ -108,7 +124,21 @@ app.factory('ItemResource', (Resource, $http, BASE_URL) => { super.clear(); } + + autoPage (type, id) { + return this.http({ + url: `${this.BASE_URL}/items`, + method: 'GET', + params: { + type: type, + id: id, + offset: this.size(), + limit: this.batchSize + } + }); + } + } - return new ItemResource($http, BASE_URL); + return new ItemResource($http, BASE_URL, ITEM_BATCH_SIZE); }); \ No newline at end of file diff --git a/js/tests/unit/controller/ContentControllerSpec.js b/js/tests/unit/controller/ContentControllerSpec.js index 8757cb5c5..d814a56de 100644 --- a/js/tests/unit/controller/ContentControllerSpec.js +++ b/js/tests/unit/controller/ContentControllerSpec.js @@ -12,7 +12,8 @@ describe('ContentController', () => { beforeEach(module('News', ($provide) => { - $provide.value('BASE_URL', 'base'); + $provide.constant('BASE_URL', 'base'); + $provide.constant('ITEM_BATCH_SIZE', 5); })); @@ -78,14 +79,22 @@ describe('ContentController', () => { 'items': [{ id: 3, feedId: 4 + }, + { + id: 5, + feedId: 4, + keepUnread: true }] }, }); ctrl.markRead(3); + ctrl.markRead(5); expect(ItemResource.markItemRead).toHaveBeenCalledWith(3); expect(FeedResource.markItemOfFeedRead).toHaveBeenCalledWith(4); + expect(ItemResource.markItemRead.callCount).toBe(1); + expect(FeedResource.markItemOfFeedRead.callCount).toBe(1); })); @@ -209,4 +218,184 @@ describe('ContentController', () => { })); + it('should mark multiple items read', inject(($controller, + ItemResource, FeedResource, Publisher) => { + + Publisher.subscribe(ItemResource).toChannels('items'); + ItemResource.markItemsRead = jasmine.createSpy('markRead'); + FeedResource.markItemOfFeedRead = jasmine.createSpy('markRead'); + + let ctrl = $controller('ContentController', { + ItemResource: ItemResource, + FeedResource: FeedResource, + data: { + 'items': [{ + id: 3, + feedId: 4 + }, + { + id: 2, + feedId: 4, + keepUnread: true + }, + { + id: 1, + feedId: 4 + },] + }, + }); + + ctrl.scrollRead([3, 2, 1]); + + expect(ItemResource.markItemsRead).toHaveBeenCalledWith([3, 1]); + expect(FeedResource.markItemOfFeedRead.callCount).toBe(2); + })); + + + it('should not autopage if less than 0 elements', inject(( + $controller, ItemResource, Publisher) => { + + let $route = { + current: { + $$route: { + type: 3 + } + } + }; + + let $routeParams = { + id: 2 + }; + + Publisher.subscribe(ItemResource).toChannels('items'); + ItemResource.autoPage = jasmine.createSpy('autoPage') + .andCallFake(() => { + return { + success: (callback) => { + callback({ + 'items': [] + }); + + return { + error: () => {} + }; + } + } + }); + + let ctrl = $controller('ContentController', { + $routeParams: $routeParams, + $route: $route, + Publisher: Publisher, + ItemResource: ItemResource, + data: {}, + }); + + expect(ctrl.autoPagingEnabled()).toBe(true); + + ctrl.autoPage(); + + expect(ctrl.autoPagingEnabled()).toBe(false); + + expect(ItemResource.autoPage).toHaveBeenCalledWith(3, 2); + + })); + + + it('should autopage if more than 0 elements', inject(( + $controller, ItemResource, Publisher) => { + + let $route = { + current: { + $$route: { + type: 3 + } + } + }; + + let $routeParams = { + id: 2 + }; + + Publisher.subscribe(ItemResource).toChannels('items'); + ItemResource.autoPage = jasmine.createSpy('autoPage') + .andCallFake(() => { + return { + success: (callback) => { + callback({ + 'items': [{items: [{id: 3}]}] + }); + + return { + error: () => {} + }; + } + } + }); + + let ctrl = $controller('ContentController', { + $routeParams: $routeParams, + $route: $route, + Publisher: Publisher, + ItemResource: ItemResource, + data: {}, + }); + + expect(ctrl.autoPagingEnabled()).toBe(true); + + ctrl.autoPage(); + + expect(ctrl.autoPagingEnabled()).toBe(true); + expect(ItemResource.size()).toBe(1); + })); + + + it('should autopage if error', inject(( + $controller, ItemResource, Publisher) => { + + let $route = { + current: { + $$route: { + type: 3 + } + } + }; + + let $routeParams = { + id: 2 + }; + + Publisher.subscribe(ItemResource).toChannels('items'); + ItemResource.autoPage = jasmine.createSpy('autoPage') + .andCallFake(() => { + return { + success: (callback) => { + callback({ + 'items': [] + }); + + return { + error: (callback) => { + callback(); + } + }; + } + } + }); + + let ctrl = $controller('ContentController', { + $routeParams: $routeParams, + $route: $route, + Publisher: Publisher, + ItemResource: ItemResource, + data: {}, + }); + + expect(ctrl.autoPagingEnabled()).toBe(true); + + ctrl.autoPage(); + + expect(ctrl.autoPagingEnabled()).toBe(true); + })); + }); diff --git a/js/tests/unit/controller/NavigationControllerSpec.js b/js/tests/unit/controller/NavigationControllerSpec.js index 653dfea26..433a40e3f 100644 --- a/js/tests/unit/controller/NavigationControllerSpec.js +++ b/js/tests/unit/controller/NavigationControllerSpec.js @@ -14,6 +14,7 @@ describe('NavigationController', () => { beforeEach(module('News', ($provide) => { $provide.value('BASE_URL', 'base'); + $provide.constant('ITEM_BATCH_SIZE', 5); })); beforeEach(inject(($controller) => { diff --git a/js/tests/unit/service/ItemResourceSpec.js b/js/tests/unit/service/ItemResourceSpec.js index 50454d41a..f93d92caa 100644 --- a/js/tests/unit/service/ItemResourceSpec.js +++ b/js/tests/unit/service/ItemResourceSpec.js @@ -14,6 +14,7 @@ describe('ItemResource', () => { beforeEach(module('News', ($provide) => { $provide.value('BASE_URL', 'base'); + $provide.constant('ITEM_BATCH_SIZE', 5); })); beforeEach(inject(($httpBackend) => { @@ -59,6 +60,34 @@ describe('ItemResource', () => { })); + it ('should mark multiple item as read', inject((ItemResource) => { + http.expectPOST('base/items/read/multiple', { + itemIds: [3, 4] + }).respond(200, {}); + + ItemResource.receive([ + { + id: 3, + feedId: 4, + unread: true + }, + { + id: 4, + feedId: 3, + unread: true + } + ], 'items'); + + ItemResource.markItemsRead([3, 4]); + + http.flush(); + + expect(ItemResource.get(3).unread).toBe(false); + expect(ItemResource.get(4).unread).toBe(false); + })); + + + it ('should star item', inject((ItemResource) => { http.expectPOST('base/items/4/a/star', {isStarred: true}) .respond(200, {}); @@ -169,6 +198,34 @@ describe('ItemResource', () => { })); + it ('should auto page', inject((ItemResource) => { + http.expectGET('base/items?id=4&limit=5&offset=3&type=3') + .respond(200, {}); + + ItemResource.receive([ + { + id: 3, + feedId: 4, + unread: true + }, + { + id: 4, + feedId: 3, + unread: true + }, + { + id: 5, + feedId: 4, + unread: true + } + ], 'items'); + + ItemResource.autoPage(3, 4); + + http.flush(); + })); + + afterEach(() => { http.verifyNoOutstandingExpectation(); http.verifyNoOutstandingRequest(); -- cgit v1.2.3