diff options
author | Bernhard Posselt <dev@bernhard-posselt.com> | 2014-05-22 15:09:57 +0200 |
---|---|---|
committer | Bernhard Posselt <dev@bernhard-posselt.com> | 2014-05-22 15:09:57 +0200 |
commit | 2bb6bc03b5c7c2eb6465d2c05159028ede6c93b4 (patch) | |
tree | 4db98c30abef529df257ed37ba293ca028fbfdf2 /js | |
parent | 7d15bfa996e7661e450070715a96fe8cb2d75a8c (diff) |
add autopaging directive
Diffstat (limited to 'js')
-rw-r--r-- | js/.jshintrc | 2 | ||||
-rw-r--r-- | js/README.md | 9 | ||||
-rw-r--r-- | js/build/app.js | 217 | ||||
-rw-r--r-- | js/controller/ContentController.js | 2 | ||||
-rw-r--r-- | js/directive/NewsScroll.js | 110 | ||||
-rw-r--r-- | js/service/ItemResource.js | 33 | ||||
-rw-r--r-- | js/tests/unit/controller/ContentControllerSpec.js | 13 | ||||
-rw-r--r-- | js/tests/unit/service/ItemResourceSpec.js | 68 | ||||
-rw-r--r-- | js/utility/Iterators.js | 19 |
9 files changed, 458 insertions, 15 deletions
diff --git a/js/.jshintrc b/js/.jshintrc index cd85aa807..d4d7e6279 100644 --- a/js/.jshintrc +++ b/js/.jshintrc @@ -18,7 +18,7 @@ "unused": true, "strict": true, "maxparams": false, - "maxdepth": 3, + "maxdepth": 4, "maxlen": 80, "browser": true, "devel": true, diff --git a/js/README.md b/js/README.md index b529fbf6a..e0fb435eb 100644 --- a/js/README.md +++ b/js/README.md @@ -29,6 +29,15 @@ The following iterators are defined and availabe: } ``` +* **reverse**: + + ```js + // iterate over list in reverse order + for (let value of reverse(list)) { + console.log(`value is: ${value}`) + } + ``` + ## Building Watch mode: diff --git a/js/build/app.js b/js/build/app.js index 38ed97fda..dc62566aa 100644 --- a/js/build/app.js +++ b/js/build/app.js @@ -182,6 +182,7 @@ var $__build_47_app__ = function () { 'data', function (Publisher, FeedResource, ItemResource, data) { 'use strict'; + ItemResource.clear(); Publisher.publishAll(data); this.getItems = function () { return ItemResource.getAll(); @@ -247,9 +248,21 @@ var $__build_47_app__ = function () { BASE_URL ]); this.starredCount = 0; + this.highestId = 0; + this.lowestId = 0; }; var $ItemResource = ItemResource; $traceurRuntime.createClass(ItemResource, { + add: function (obj) { + var id = obj[$traceurRuntime.toProperty(this.id)]; + if (this.highestId < id) { + this.highestId = id; + } + if (this.lowestId === 0 || this.lowestId > id) { + this.lowestId = id; + } + $traceurRuntime.superCall(this, $ItemResource.prototype, 'add', [obj]); + }, receive: function (value, channel) { switch (channel) { case 'newestItemId': @@ -325,9 +338,20 @@ var $__build_47_app__ = function () { } return this.http.post(this.BASE_URL + '/items/read'); }, + getHighestId: function () { + return this.highestId; + }, + getLowestId: function () { + return this.lowestId; + }, keepUnread: function (itemId) { this.get(itemId).keepUnread = true; return this.read(itemId, false); + }, + clear: function () { + this.highestId = 0; + this.lowestId = 0; + $traceurRuntime.superCall(this, $ItemResource.prototype, 'clear', []); } }, {}, Resource); return new ItemResource($http, BASE_URL); @@ -830,6 +854,199 @@ var $__build_47_app__ = function () { writable: true }), $__2; }; + window.reverse = function (list) { + 'use strict'; + var $__2; + return $__2 = {}, Object.defineProperty($__2, Symbol.iterator, { + value: function () { + return $traceurRuntime.initGeneratorFunction(function $__9() { + var counter, $counter; + return $traceurRuntime.createGeneratorInstance(function ($ctx) { + while (true) + switch ($ctx.state) { + case 0: + $ctx.pushTry(28, null); + $ctx.state = 31; + break; + case 31: + throw undefined; + $ctx.state = 33; + break; + case 33: + $ctx.popTry(); + $ctx.state = -2; + break; + case 28: + $ctx.popTry(); + $counter = $ctx.storedException; + $ctx.state = 26; + break; + case 26: + $counter = list.length; + $ctx.state = 27; + break; + case 27: + $ctx.state = $counter >= 0 ? 17 : -2; + break; + case 22: + $counter -= 1; + $ctx.state = 27; + break; + case 17: + $ctx.pushTry(15, null); + $ctx.state = 18; + break; + case 18: + throw undefined; + $ctx.state = 20; + break; + case 20: + $ctx.popTry(); + $ctx.state = 22; + break; + case 15: + $ctx.popTry(); + counter = $ctx.storedException; + $ctx.state = 13; + break; + case 13: + counter = $counter; + $ctx.state = 14; + break; + case 14: + $ctx.pushTry(null, 6); + $ctx.state = 8; + break; + case 8: + $ctx.state = 2; + return list[$traceurRuntime.toProperty(counter)]; + case 2: + $ctx.maybeThrow(); + $ctx.state = 22; + break; + case 6: + $ctx.popTry(); + $ctx.state = 12; + break; + case 12: + $counter = counter; + $ctx.state = 10; + break; + default: + return $ctx.end(); + } + }, $__9, this); + })(); + }, + configurable: true, + enumerable: true, + writable: true + }), $__2; + }; + app.directive('newsScroll', [ + '$timeout', + function ($timeout) { + 'use strict'; + return { + restrict: 'A', + scope: { + 'newsScrollAutoPage': '&', + 'newsScrollMarkRead': '&', + 'newsScrollDisabledMarkRead': '=', + 'newsScrollDisabledAutoPage': '=', + 'newsScrollMarkReadTimeout': '@', + 'newsScrollTimeout': '@', + 'newsScrollAutoPageWhenLeft': '@', + 'newsScrollItemsSelector': '@' + }, + link: function (scope, elem) { + var scrolling = false; + scope.newsScrollTimeout = scope.newsScrollTimeout || 1; + scope.newsScrollMarkReadTimeout = scope.newsScrollMarkReadTimeout || 1; + scope.newsScrollAutoPageWhenLeft = scope.newsScrollAutoPageWhenLeft || 50; + scope.newsScrollItemsSelector = scope.newsScrollItemsSelector || '.items'; + elem.on('scroll', function () { + if (!scrolling) { + try { + throw undefined; + } catch (markRead) { + try { + throw undefined; + } catch (items) { + scrolling = true; + $timeout(function () { + scrolling = false; + }, scope.newsScrollTimeout * 1000); + items = $(scope.newsScrollItemsSelector); + if (!scope.newsScrollDisabledAutoPage) { + try { + throw undefined; + } catch (counter) { + counter = 0; + for (var $__3 = reverse(items.find('.feed_item'))[$traceurRuntime.toProperty(Symbol.iterator)](), $__4; !($__4 = $__3.next()).done;) { + try { + throw undefined; + } catch (item) { + item = $__4.value; + { + item = $(item); + if (counter >= scope.newsScrollAutoPageWhenLeft) { + break; + } + if (item.position().top < 0) { + scope.newsScrollAutoPage(); + break; + } + counter += 1; + } + } + } + } + } + markRead = function () { + if (!scope.newsScrollDisabledMarkRead) { + try { + throw undefined; + } catch (unread) { + try { + throw undefined; + } catch (ids) { + ids = []; + unread = items.find('.feed_item:not(.read)'); + for (var $__5 = unread[$traceurRuntime.toProperty(Symbol.iterator)](), $__6; !($__6 = $__5.next()).done;) { + try { + throw undefined; + } catch (item) { + item = $__6.value; + { + item = $(item); + if (item.position().top <= -50) { + ids.push(parseInt($(item).data('id'), 10)); + } else { + break; + } + } + } + } + scope.newsScrollMarkRead(ids); + } + } + } + }; + $timeout(function () { + markRead(); + }, scope.newsScrollMarkReadTimeout * 1000); + } + } + } + }); + scope.$on('$destroy', function () { + element.off('scroll'); + }); + } + }; + } + ]); }(window, document, angular, jQuery, OC, oc_requesttoken)); return {}; }();
\ No newline at end of file diff --git a/js/controller/ContentController.js b/js/controller/ContentController.js index ae5116dbb..796fea71a 100644 --- a/js/controller/ContentController.js +++ b/js/controller/ContentController.js @@ -11,6 +11,8 @@ app.controller('ContentController', function (Publisher, FeedResource, ItemResource, data) { 'use strict'; + ItemResource.clear(); + // distribute data to models based on key Publisher.publishAll(data); diff --git a/js/directive/NewsScroll.js b/js/directive/NewsScroll.js new file mode 100644 index 000000000..5972cce67 --- /dev/null +++ b/js/directive/NewsScroll.js @@ -0,0 +1,110 @@ +/** + * ownCloud - News + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Bernhard Posselt <dev@bernhard-posselt.com> + * @copyright Bernhard Posselt 2014 + */ +app.directive('newsScroll', ($timeout) => { + 'use strict'; + + return { + restrict: 'A', + scope: { + 'newsScrollAutoPage': '&', + 'newsScrollMarkRead': '&', + 'newsScrollDisabledMarkRead': '=', + 'newsScrollDisabledAutoPage': '=', + 'newsScrollMarkReadTimeout': '@', // optional, defaults to 1 second + 'newsScrollTimeout': '@', // optional, defaults to 1 second + 'newsScrollAutoPageWhenLeft': '@', // optional, defaults to 50 + 'newsScrollItemsSelector': '@' // optional, defaults to .items + }, + link: (scope, elem) => { + let scrolling = false; + + scope.newsScrollTimeout = scope.newsScrollTimeout || 1; + scope.newsScrollMarkReadTimeout = + scope.newsScrollMarkReadTimeout || 1; + scope.newsScrollAutoPageWhenLeft = + scope.newsScrollAutoPageWhenLeft || 50; + scope.newsScrollItemsSelector = + scope.newsScrollItemsSelector || '.items'; + + + elem.on('scroll', () => { + + // only allow one scrolling event to trigger + if (!scrolling) { + scrolling = true; + + $timeout(() => { + scrolling = false; + }, scope.newsScrollTimeout*1000); + + + let items = $(scope.newsScrollItemsSelector); + + // autopaging + if (!scope.newsScrollDisabledAutoPage) { + let counter = 0; + for (let item of reverse(items.find('.feed_item'))) { + item = $(item); + + + // if the counter is higher than the size it means + // that it didnt break to auto page yet and that + // there are more items, so break + if (counter >= scope.newsScrollAutoPageWhenLeft) { + break; + } + + // this is only reached when the item is not is + // below the top and we didnt hit the factor yet so + // autopage and break + if (item.position().top < 0) { + scope.newsScrollAutoPage(); + break; + } + + counter += 1; + } + } + + // mark read + let markRead = () => { + if (!scope.newsScrollDisabledMarkRead) { + let ids = []; + let unread = items.find('.feed_item:not(.read)'); + + for (let item of unread) { + item = $(item); + + if (item.position().top <= -50) { + ids.push(parseInt($(item).data('id'), 10)); + } else { + break; + } + } + + scope.newsScrollMarkRead(ids); + } + }; + + // allow user to undo accidental scroll + $timeout(() => { + markRead(); + }, scope.newsScrollMarkReadTimeout*1000); + } + + }); + + // remove scroll handler if element is destroyed + scope.$on('$destroy', () => { + element.off('scroll'); + }); + } + }; +});
\ No newline at end of file diff --git a/js/service/ItemResource.js b/js/service/ItemResource.js index dc74f637a..67a6d23e6 100644 --- a/js/service/ItemResource.js +++ b/js/service/ItemResource.js @@ -16,6 +16,23 @@ app.factory('ItemResource', (Resource, $http, BASE_URL) => { constructor ($http, BASE_URL) { super($http, BASE_URL); this.starredCount = 0; + this.highestId = 0; + this.lowestId = 0; + } + + + add (obj) { + let id = obj[this.id]; + + if (this.highestId < id) { + this.highestId = id; + } + + if (this.lowestId === 0 || this.lowestId > id) { + this.lowestId = id; + } + + super.add(obj); } @@ -95,12 +112,28 @@ app.factory('ItemResource', (Resource, $http, BASE_URL) => { } + getHighestId () { + return this.highestId; + } + + + getLowestId () { + return this.lowestId; + } + + keepUnread (itemId) { this.get(itemId).keepUnread = true; return this.read(itemId, false); } + clear () { + this.highestId = 0; + this.lowestId = 0; + super.clear(); + } + } return new ItemResource($http, BASE_URL); diff --git a/js/tests/unit/controller/ContentControllerSpec.js b/js/tests/unit/controller/ContentControllerSpec.js index 422bd21f4..20dd3c71f 100644 --- a/js/tests/unit/controller/ContentControllerSpec.js +++ b/js/tests/unit/controller/ContentControllerSpec.js @@ -43,4 +43,17 @@ describe('ContentController', () => { expect(controller.getFeeds().length).toBe(1); })); + + it('should clear data on url change', inject(($controller, + ItemResource) => { + + ItemResource.clear = jasmine.createSpy('clear'); + + $controller('ContentController', { + data: {} + }); + + expect(ItemResource.clear).toHaveBeenCalled(); + })); + }); diff --git a/js/tests/unit/service/ItemResourceSpec.js b/js/tests/unit/service/ItemResourceSpec.js index 0316d39cc..bfdaf4b4a 100644 --- a/js/tests/unit/service/ItemResourceSpec.js +++ b/js/tests/unit/service/ItemResourceSpec.js @@ -35,20 +35,6 @@ describe('ItemResource', () => { })); - it('should receive items', inject((ItemResource) => { - ItemResource.receive([ - { - id: 3 - }, - { - id: 4 - } - ], 'items'); - - expect(ItemResource.size()).toBe(2); - })); - - it ('should keep item unread', inject((ItemResource) => { http.expectPOST('base/items/3/read', {isRead: false}).respond(200, {}); @@ -186,6 +172,60 @@ describe('ItemResource', () => { })); + it ('should remember the highest id', inject((ItemResource) => { + ItemResource.receive([ + { + id: 3, + }, + { + id: 5, + }, + { + id: 4, + } + ], 'items'); + + expect(ItemResource.getHighestId()).toBe(5); + })); + + + it ('should remember the lowest id', inject((ItemResource) => { + ItemResource.receive([ + { + id: 3, + }, + { + id: 5, + }, + { + id: 4, + } + ], 'items'); + + expect(ItemResource.getLowestId()).toBe(3); + })); + + + it ('should clear the highest and lowest id', inject((ItemResource) => { + ItemResource.receive([ + { + id: 3, + }, + { + id: 5, + }, + { + id: 4, + } + ], 'items'); + + ItemResource.clear(); + + expect(ItemResource.getHighestId()).toBe(0); + expect(ItemResource.getLowestId()).toBe(0); + })); + + afterEach(() => { http.verifyNoOutstandingExpectation(); http.verifyNoOutstandingRequest(); diff --git a/js/utility/Iterators.js b/js/utility/Iterators.js index 3c5e34b18..1f96aab71 100644 --- a/js/utility/Iterators.js +++ b/js/utility/Iterators.js @@ -48,4 +48,23 @@ window.enumerate = function (list) { })(); } }; +}; + + +/** + * Iterates over a list in reverse + * like: for (let value of reverse(list)) + */ +window.reverse = function (list) { + 'use strict'; + + return { + [Symbol.iterator]: function () { + return (function*() { + for (let counter = list.length; counter >= 0 ; counter -= 1) { + yield list[counter]; + } + })(); + } + }; };
\ No newline at end of file |